├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── cloudscribe.SimpleAuth.sln ├── images └── screenshot-simleauth-with-recaptcha.jpg ├── samples ├── basic.WebApp │ ├── basic.WebApp.sln │ └── src │ │ └── basic.WebApp │ │ ├── .bowerrc │ │ ├── Controllers │ │ └── HomeController.cs │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── ReadMe.md │ │ ├── Startup.cs │ │ ├── Views │ │ ├── Home │ │ │ ├── About.cshtml │ │ │ ├── Contact.cshtml │ │ │ └── Index.cshtml │ │ ├── Login │ │ │ ├── HashPassword.cshtml │ │ │ └── Index.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ ├── _Layout.cshtml │ │ │ ├── _LoginPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ │ ├── appsettings.json │ │ ├── basic.WebApp.csproj │ │ ├── bower.json │ │ ├── gulpfile.js │ │ ├── package.json │ │ ├── simpleauthsettings.json │ │ └── wwwroot │ │ ├── _references.js │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── images │ │ ├── ASP-NET-Banners-01.png │ │ ├── ASP-NET-Banners-02.png │ │ ├── Banner-01-Azure.png │ │ └── Banner-02-VS.png │ │ ├── js │ │ └── site.js │ │ └── web.config └── multiTenant.WebApp │ ├── multiTenant.WebApp.sln │ └── src │ └── multiTenant.WebApp │ ├── .bowerrc │ ├── AppTenant.cs │ ├── AppTenantAuthSettingsResolver.cs │ ├── AppTenantUserLookupProvider.cs │ ├── CachingAppTenantResolver.cs │ ├── Controllers │ └── HomeController.cs │ ├── MultiTenancyOptions.cs │ ├── Properties │ └── launchSettings.json │ ├── ReadMe.md │ ├── Startup.cs │ ├── TenantViewLocationExpander.cs │ ├── Themes │ ├── Cerulean │ │ ├── Home │ │ │ ├── About.cshtml │ │ │ ├── Contact.cshtml │ │ │ └── Index.cshtml │ │ └── Shared │ │ │ └── _Layout.cshtml │ ├── Darkly │ │ ├── Home │ │ │ ├── About.cshtml │ │ │ ├── Contact.cshtml │ │ │ └── Index.cshtml │ │ └── Shared │ │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml │ ├── Views │ ├── Home │ │ ├── About.cshtml │ │ ├── Contact.cshtml │ │ └── Index.cshtml │ ├── Login │ │ ├── HashPassword.cshtml │ │ └── Index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ ├── _LoginPartial.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml │ ├── appsettings.json │ ├── bower.json │ ├── gulpfile.js │ ├── multiTenant.WebApp.csproj │ ├── package.json │ ├── simpleauthsettings.json │ └── wwwroot │ ├── _references.js │ ├── css │ ├── site.css │ └── themes │ │ ├── cerulean.css │ │ └── darkly.css │ ├── favicon.ico │ ├── images │ ├── ASP-NET-Banners-01.png │ ├── ASP-NET-Banners-02.png │ ├── Banner-01-Azure.png │ └── Banner-02-VS.png │ ├── js │ └── site.js │ └── web.config └── src ├── cloudscribe.Web.SimpleAuth ├── ClaimsPrincipalExtensions.cs ├── Controllers │ └── LoginController.cs ├── Models │ ├── IAuthSettingsResolver.cs │ ├── IUserLookupProvider.cs │ ├── RecaptchaResponse.cs │ ├── SimpleAuthClaim.cs │ ├── SimpleAuthSettings.cs │ └── SimpleAuthUser.cs ├── Properties │ └── AssemblyInfo.cs ├── ServiceCollectionExtensions.cs ├── Services │ ├── DefaultAuthSettingsResolver.cs │ ├── DefaultUserLookupProvider.cs │ └── SignInManager.cs ├── ViewModels │ ├── HashPasswordViewModel.cs │ └── LoginViewModel.cs ├── Views │ ├── Login │ │ ├── HashPassword.cshtml │ │ └── Index.cshtml │ └── Shared │ │ ├── _LoginPartial.cshtml │ │ └── _ValidationScriptsPartial.cshtml └── cloudscribe.Web.SimpleAuth.csproj └── example.WebApp ├── .bowerrc ├── AppTenant.cs ├── AppTenantAuthSettingsResolver.cs ├── AppTenantUserLookupProvider.cs ├── CachingAppTenantResolver.cs ├── Controllers └── HomeController.cs ├── MultiTenancyOptions.cs ├── Program.cs ├── Project_Readme.html ├── Properties └── launchSettings.json ├── Startup.cs ├── TenantViewLocationExpander.cs ├── Themes ├── Cerulean │ ├── Home │ │ └── Index_tenant-1.cshtml │ └── Shared │ │ └── _Layout.cshtml ├── Darkly │ ├── Home │ │ └── About.cshtml │ └── Shared │ │ └── _Layout.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── Views ├── Home │ ├── About.cshtml │ ├── Administration.cshtml │ ├── Contact.cshtml │ ├── Index.cshtml │ └── Members.cshtml ├── Shared │ ├── Components │ │ └── Navigation │ │ │ ├── BootstrapBreadcrumbs.cshtml │ │ │ ├── BootstrapTopNav.cshtml │ │ │ ├── BootstrapTopNavWithDropdowns.cshtml │ │ │ ├── ChildTree.cshtml │ │ │ ├── Default.cshtml │ │ │ └── SideNav.cshtml │ ├── Error.cshtml │ ├── NavigationNodeChildDropdownPartial.cshtml │ ├── NavigationNodeChildTreePartial.cshtml │ ├── NavigationNodeSideNavPartial.cshtml │ ├── _Layout.cshtml │ └── _ValidationScriptsPartial.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── appsettings.json ├── bower.json ├── bundleconfig.json ├── example.WebApp.csproj ├── navigation.xml ├── package.json ├── simpleauthsettings.json ├── web.config └── wwwroot ├── _references.js ├── css ├── site.css └── themes │ ├── cerulean.css │ └── darkly.css ├── favicon.ico ├── images ├── banner1.svg ├── banner2.svg ├── banner3.svg └── banner4.svg └── js └── site.js /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | 238 | 239 | src/cloudscribe.Web.Navigation/project.lock.json 240 | **/wwwroot/lib* 241 | 242 | 243 | # src/NavigationDemo.Web/project.lock.json 244 | # test/cloudscribe.Web.Navigation.Test/project.lock.json 245 | src/example.WebApp/appsettings.local.overrides.json 246 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: cloudscribe.SimpleAuth.sln 3 | sudo: false 4 | dist: trusty 5 | mono: none 6 | dotnet: 1.0.4 7 | env: 8 | global: 9 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 10 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 11 | os: 12 | - linux 13 | - osx 14 | osx_image: xcode8.3 15 | script: 16 | - if test "$TRAVIS_OS_NAME" == "linux"; then dotnet restore; fi 17 | - if test "$TRAVIS_OS_NAME" == "osx"; then dotnet restore --disable-parallel; fi 18 | - dotnet build -c Release 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloudscribe.Web.SimpleAuth 2 | 3 | Simple Authentication for ASP.NET Core - because sometimes less is more 4 | 5 | ### Build Status 6 | 7 | | Windows | Linux/Mac | 8 | | ------------- | ------------- | 9 | | [![Build status](https://ci.appveyor.com/api/projects/status/fqy94xg8y5e2px4l/branch/master?svg=true)](https://ci.appveyor.com/project/joeaudette/cloudscribe-web-simpleauth/branch/master) | [![Build Status](https://travis-ci.org/cloudscribe/cloudscribe.Web.SimpleAuth.svg?branch=master)](https://travis-ci.org/cloudscribe/cloudscribe.Web.SimpleAuth) | 10 | 11 | 12 | #### You should not use SimpleAuth if any of these requirements apply to your web application or website project: 13 | * You need more than a very small number of users to be able to authenticate on the site 14 | * You need users to be able to self register using a registration page 15 | 16 | If either of the above are true of your project then you should probably look at these other projects instead of SimpleAuth: 17 | 18 | * [ASP.NET Identity](https://github.com/aspnet/Identity) 19 | * [IdentityServer4](https://github.com/IdentityServer/IdentityServer4) 20 | * [cloudscribe.Core](https://github.com/joeaudette/cloudscribe) - one of my other projects :-D 21 | 22 | See also the [Complete List of cloudscribe Libraries](https://www.cloudscribe.com/docs/complete-list-of-cloudscribe-libraries) 23 | 24 | #### So what scenarios is SimpleAuth good for? 25 | * Personal sites or blogs where only one or a few people need to login 26 | * Brochure sites where only one or a few people need to login 27 | * Demo sites or prototypes where you only need a few users to be able to login 28 | 29 | #### What is the value proposition? 30 | * No database required for user accounts 31 | * Supports multiple tenants based on host name 32 | * Easy configuration - settings and users are stored in a simple json configuration file 33 | * Built in support for [Recaptcha](https://www.google.com/recaptcha/intro/index.html) -just add your keys in configuration 34 | * Supports custom role and claims configuration for use with [custom authorization policies](https://docs.asp.net/en/latest/security/authorization/policies.html) 35 | * Supports hashed passwords using the PasswordHasher from ASP.NET Identity 36 | * Supports clear text passwords so you can get started easily but you should use hashed passwords 37 | * PasswordHashing utitlity built in at /Login/HashPassword so you can let your few users generate their own hash that you can add to the config file. The utility can be disabled and will return a 404 if disabled. 38 | * Lightweight 39 | * Very simple implementation without many moving parts 40 | 41 | 42 | #### Installation 43 | 44 | The example.WebApp project in this solution is the best guide to setup and configuration, I've added some comments in the Startup.cs that should be useful, and if you have any trouble setting it up in your app, you can compare against the demo to see what is missing or different. Note that the example.WebApp also uses cloudscribe.Navigation for the menu, and you may also want to use it. 45 | 46 | The basic installaiton steps are as follows: 47 | 48 | - [] 1 add this in your project.json: 49 | 50 | "cloudscribe.Web.SimpleAuth": "1.0.0-* 51 | 52 | Visual Studio should automatically pull it in from [nuget.org](https://www.nuget.org/packages/cloudscribe.Web.SimpleAuth), but you can also run dotnet restore --no-cache from the command line in the solution or project folder. 53 | 54 | - [] 2 copy the simpleauthsettings.json file from the example.WebApp project into your own and use it to configure settings and users 55 | 56 | - [] 3 add this using statement to the top of your Startup.cs 57 | 58 | using cloudscribe.Web.SimpleAuth.Models; 59 | using Microsoft.Extensions.OptionsModel; 60 | using Microsoft.AspNet.Identity; // this is only used for the password hasher 61 | 62 | - [] 4 add this in the StartupMethod of your Startup.cs 63 | 64 | builder.AddJsonFile("simpleauthsettings.json", optional: true); 65 | 66 | - [] 5 add this in the ConfigureServices method of your Startup.cs 67 | 68 | services.Configure(Configuration.GetSection("SimpleAuthSettings")); 69 | services.Configure>(Configuration.GetSection("Users")); 70 | services.AddScoped, PasswordHasher>(); 71 | 72 | - [] 6 change the signature of the Configure method in your Startup.cs so the DI can inject the SimpleAuthSettings accessor into that method 73 | 74 | public void Configure( 75 | IApplicationBuilder app, 76 | IHostingEnvironment env, 77 | ILoggerFactory loggerFactory, 78 | IOptions authSettingsAccessor 79 | ) 80 | { 81 | ... 82 | 83 | // Add cookie-based authentication to the request pipeline 84 | 85 | SimpleAuthSettings authSettings = authSettingsAccessor.Value; 86 | 87 | var ApplicationCookie = new CookieAuthenticationOptions 88 | { 89 | AuthenticationScheme = authSettings.AuthenticationScheme, 90 | CookieName = authSettings.AuthenticationScheme, 91 | AutomaticAuthenticate = true, 92 | AutomaticChallenge = true, 93 | LoginPath = new PathString("/Login/Index"), 94 | Events = new CookieAuthenticationEvents 95 | { 96 | //OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync 97 | } 98 | }; 99 | 100 | app.UseCookieAuthentication(ApplicationCookie); 101 | 102 | // make sure authentication is configured before MVC 103 | app.UseMvc(); 104 | 105 | } 106 | 107 | 108 | 109 | - [] 7 copy the [Views/Login](https://github.com/joeaudette/cloudscribe.Web.SimpleAuth/tree/master/src/cloudscribe.Web.SimpleAuth/Views) folder from the cloudscribe.SimpleAuth.Web project into your own project 110 | 111 | - [] 8 study the example.WebApp for examples of how to configure authorization policies with roles that you configure for users in the simpleauthsettings.json file 112 | 113 | - [] 9 you will of course have to provide a link in your app to the login page, look at the _LoginPartial.cshtml in the Views/Shared folder of cloudscribe.Web.SimpleAuth, you should add that to your [_Layout.cshtml](https://github.com/joeaudette/cloudscribe.Web.SimpleAuth/blob/master/src/example.WebApp/Views/Shared/_Layout.cshtml) 114 | 115 | The views are also included as embedded resources so if you don't need to customize them you can add them like this: 116 | 117 | services.AddMvc() 118 | .AddRazorOptions(options => 119 | { 120 | options.AddEmbeddedViewsForSimpleAuth(); 121 | 122 | }); 123 | 124 | 125 | ![Screenshot](/images/screenshot-simleauth-with-recaptcha.jpg?raw=true) 126 | 127 | 128 | ##### Keep In Touch 129 | 130 | If you are interested in hiring us for consulting or other support services related to the cloudscribe set of projects, please send an email to info [at] cloudscribe.com. 131 | 132 | We're also on twitter @cloudscribeweb 133 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | environment: 3 | nodejs_version: "6.9.1" 4 | # Install scripts. (runs after repo cloning) 5 | install: 6 | # Get the latest stable version of Node.js or io.js 7 | - ps: Install-Product node $env:nodejs_version 8 | - ps: $env:BuildNumber= $env:APPVEYOR_BUILD_NUMBER 9 | init: 10 | - git config --global core.autocrlf true 11 | build_script: 12 | - dotnet restore 13 | - dotnet build -c Release 14 | test: off 15 | 16 | 17 | -------------------------------------------------------------------------------- /cloudscribe.SimpleAuth.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4C7ABA2B-6A7D-46E4-9454-F262F2E8BCA7}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8565BC2E-DBB1-4E37-A873-07D3ABDD8247}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cloudscribe.Web.SimpleAuth", "src\cloudscribe.Web.SimpleAuth\cloudscribe.Web.SimpleAuth.csproj", "{754228FB-2122-4D86-81EB-774D7E713B9F}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "example.WebApp", "src\example.WebApp\example.WebApp.csproj", "{40178A22-F205-4FCD-8774-0A2535690872}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {754228FB-2122-4D86-81EB-774D7E713B9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {754228FB-2122-4D86-81EB-774D7E713B9F}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {754228FB-2122-4D86-81EB-774D7E713B9F}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {754228FB-2122-4D86-81EB-774D7E713B9F}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {40178A22-F205-4FCD-8774-0A2535690872}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {40178A22-F205-4FCD-8774-0A2535690872}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {40178A22-F205-4FCD-8774-0A2535690872}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {40178A22-F205-4FCD-8774-0A2535690872}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(NestedProjects) = preSolution 33 | {754228FB-2122-4D86-81EB-774D7E713B9F} = {4C7ABA2B-6A7D-46E4-9454-F262F2E8BCA7} 34 | {40178A22-F205-4FCD-8774-0A2535690872} = {4C7ABA2B-6A7D-46E4-9454-F262F2E8BCA7} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /images/screenshot-simleauth-with-recaptcha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/images/screenshot-simleauth-with-recaptcha.jpg -------------------------------------------------------------------------------- /samples/basic.WebApp/basic.WebApp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26114.2 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C06644D9-4095-40C7-A06B-AF7783537B98}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "basic.WebApp", "src\basic.WebApp\basic.WebApp.csproj", "{EA004E25-6F8E-4C31-90ED-C03D844605E5}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {EA004E25-6F8E-4C31-90ED-C03D844605E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {EA004E25-6F8E-4C31-90ED-C03D844605E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {EA004E25-6F8E-4C31-90ED-C03D844605E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {EA004E25-6F8E-4C31-90ED-C03D844605E5}.Release|Any CPU.Build.0 = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(NestedProjects) = preSolution 25 | {EA004E25-6F8E-4C31-90ED-C03D844605E5} = {C06644D9-4095-40C7-A06B-AF7783537B98} 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace basic.WebApp.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | 16 | public IActionResult About() 17 | { 18 | ViewData["Message"] = "Your application description page."; 19 | 20 | return View(); 21 | } 22 | 23 | public IActionResult Contact() 24 | { 25 | ViewData["Message"] = "Your contact page."; 26 | 27 | return View(); 28 | } 29 | 30 | public IActionResult Error() 31 | { 32 | return View(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51705/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "Hosting:Environment": "Development" 16 | } 17 | }, 18 | "web": { 19 | "commandName": "web", 20 | "environmentVariables": { 21 | "Hosting:Environment": "Development" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/ReadMe.md: -------------------------------------------------------------------------------- 1 | #Basic demo 2 | 3 | this example shows the most simple configuration using SimpleAuth for a single tenant web app with the least moving parts 4 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using cloudscribe.Web.SimpleAuth.Models; 4 | using cloudscribe.Web.SimpleAuth.Services; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | 14 | namespace basic.WebApp 15 | { 16 | public class Startup 17 | { 18 | public Startup(IHostingEnvironment env) 19 | { 20 | // Set up configuration sources. 21 | var builder = new ConfigurationBuilder() 22 | .AddJsonFile("appsettings.json") 23 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); 24 | 25 | // you can use whatever file name you like and it is probably a good idea to use a custom file name 26 | // just an a small extra protection in case hackers try some kind of attack based on knowing the name of the file 27 | // it should not be possible for anyone to get files outside of wwwroot using http requests 28 | // but every little thing you can do for stronger security is a good idea 29 | builder.AddJsonFile("simpleauthsettings.json", optional: true); 30 | 31 | 32 | if (env.IsDevelopment()) 33 | { 34 | // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709 35 | builder.AddUserSecrets(); 36 | } 37 | 38 | builder.AddEnvironmentVariables(); 39 | Configuration = builder.Build(); 40 | } 41 | 42 | public IConfigurationRoot Configuration { get; set; } 43 | 44 | // This method gets called by the runtime. Use this method to add services to the container. 45 | public void ConfigureServices(IServiceCollection services) 46 | { 47 | services.Configure(Configuration.GetSection("SimpleAuthSettings")); 48 | services.Configure>(Configuration.GetSection("Users")); 49 | services.AddScoped, PasswordHasher>(); 50 | services.AddScoped(); 51 | services.AddScoped(); // single tenant 52 | services.AddScoped(); 53 | 54 | 55 | services.AddMvc(); 56 | 57 | 58 | } 59 | 60 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 61 | public void Configure( 62 | IApplicationBuilder app, 63 | IHostingEnvironment env, 64 | ILoggerFactory loggerFactory, 65 | IOptions authSettingsAccessor 66 | ) 67 | { 68 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 69 | loggerFactory.AddDebug(); 70 | 71 | if (env.IsDevelopment()) 72 | { 73 | app.UseBrowserLink(); 74 | app.UseDeveloperExceptionPage(); 75 | } 76 | else 77 | { 78 | app.UseExceptionHandler("/Home/Error"); 79 | 80 | } 81 | 82 | app.UseStaticFiles(); 83 | 84 | // Add cookie-based authentication to the request pipeline 85 | 86 | SimpleAuthSettings authSettings = authSettingsAccessor.Value; 87 | 88 | var ApplicationCookie = new CookieAuthenticationOptions 89 | { 90 | AuthenticationScheme = authSettings.AuthenticationScheme, 91 | CookieName = authSettings.AuthenticationScheme, 92 | AutomaticAuthenticate = true, 93 | AutomaticChallenge = true, 94 | LoginPath = new PathString("/Login/Index") 95 | 96 | }; 97 | 98 | app.UseCookieAuthentication(ApplicationCookie); 99 | 100 | 101 | app.UseMvc(routes => 102 | { 103 | routes.MapRoute( 104 | name: "default", 105 | template: "{controller=Home}/{action=Index}/{id?}"); 106 | }); 107 | } 108 | 109 | // Entry point for the application. 110 | public static void Main(string[] args) 111 | { 112 | var host = new WebHostBuilder() 113 | .UseKestrel() 114 | .UseContentRoot(Directory.GetCurrentDirectory()) 115 | .UseIISIntegration() 116 | .UseStartup() 117 | .Build(); 118 | 119 | host.Run(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 67 | 68 | 111 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Login/HashPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model cloudscribe.Web.SimpleAuth.ViewModels.HashPasswordViewModel 2 | 3 | 4 | @{ 5 | ViewData["Title"] = "Password Hasher"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | 11 |

Hash Password Form

12 |

13 | Simple auth user account information is stored in a simple json file. You could define users with clear text passwords, 14 | but that means anyone who can access the file, such as employees at your web host etc, could access your password. 15 | It would be better to store the passwords as hashed since it would be extremely difficult if not impossible for someone to 16 | determine the password from the hash. This utility uses the ASP.NET Identity PasswordHasher to generate the hashes. 17 | Hashes are one way encryption, they are designed to be impossible to decrypt. 18 | When you login, the password you enter is hashed and validated against the hash in storage, and if it matches authorization is granted. 19 |

20 |

21 | Note however, that if you enter the same password into the hash generator, a different hash will be generated each time, so it is not possible 22 | to just try generating hashes to see if it matches a hash that has been leaked. This makes it very hard for a hacker to get your password from 23 | the hash even if he manages to find out the hash. There is a complex algorythm with 24 | salt added to make sure the hashes are not deterministic. 25 |

26 |

27 | Keep in mind that use of this utility should only be done either from a localhost installation, or from an SSL/TLS protected endpoint. 28 | Don't hash your password while using public wifi. 29 |

30 |

31 | Don't use a password that is the same or following any pattern similar to your other passwords at other web sites. 32 | If you do that and a hacker manages to get your password from any site, he can try to guess passwords for your other website accounts. 33 |

34 |

35 | Be sure to use a password of at least 8 characters, more is better, and use upper and lower case letters as well as numbers and 36 | special characters to make sure you start with a strong password. Don't use dictionary words. We are not masking the password here so make sure no-one is 37 | shoulder surfing when you create your password hash. 38 |

39 |

40 | Once all your users have generated their hashes and their hashes have been added to the simpleauthsettings.json file, you probably should disable this page by setting 41 | EnablePasswordHasherUi as false. 42 | 43 |

44 |
45 |
46 |
47 | 48 |
49 | 50 | 51 |
52 |
53 | @if (Model.OutputHash.Length > 0) 54 | { 55 | 56 |
57 | 58 |
59 | 60 | 61 |
62 |
63 | 64 | } 65 | 66 | 67 |
68 |
69 | 70 |
71 |
72 |
-------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Login/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model cloudscribe.Web.SimpleAuth.ViewModels.LoginViewModel 2 | 3 | @if (!string.IsNullOrEmpty(Model.RecaptchaSiteKey)) 4 | { 5 | 6 | } 7 | 8 | @{ 9 | ViewData["Title"] = "Log in"; 10 | } 11 | 12 |

@ViewData["Title"].

13 |

There are 2 pre-configured example users in simpleauthsettings.json. 14 | You can login with admin/admin or demo/demo 15 |

16 |

You can of course edit that file to configure your own user accounts and remove the samples.

17 |
18 |
19 |
20 |
21 |

Use a local account to log in.

22 |
23 |
24 |
25 | 26 |
27 | 28 | 29 |
30 |
31 |
32 | 33 |
34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 |
44 |
45 |
46 | @if (Model.RecaptchaSiteKey.Length > 0) 47 | { 48 |
49 |
50 |
51 | @Html.ValidationMessage("recaptchaerror", new { @class = "text-danger" }) 52 |
53 |
54 | 55 | } 56 |
57 |
58 | 59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 | @section Scripts { 68 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 69 | } 70 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - basic.WebApp 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 41 |
42 | @RenderBody() 43 |
44 |
45 |

© 2016 - basic.WebApp

46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 63 | 64 | 65 | 66 | @RenderSection("scripts", required: false) 67 | 68 | 69 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Security.Claims 2 | @using cloudscribe.Web.SimpleAuth.Extensions 3 | 4 | @if (User.Identity.IsAuthenticated) 5 | { 6 | 14 | } 15 | else 16 | { 17 | 20 | } 21 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using basic.WebApp 2 | @using Microsoft.AspNetCore.Identity 3 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 4 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Data": { 3 | "DefaultConnection": { 4 | "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-basic.WebApp-59f3c1ef-f9bb-40a1-b788-7dcd123eff7d;Trusted_Connection=True;MultipleActiveResultSets=true" 5 | } 6 | }, 7 | "Logging": { 8 | "IncludeScopes": false, 9 | "LogLevel": { 10 | "Default": "Debug", 11 | "System": "Information", 12 | "Microsoft": "Information" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/basic.WebApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | basic.WebApp.Core-59f3c1ef-f9bb-40a1-b788-7dcd123eff7d 5 | netcoreapp1.1 6 | 7 | 8 | 9 | $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Always 30 | 31 | 32 | Always 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ASP.NET", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.5", 6 | "jquery": "2.1.4", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.4" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/gulpfile.js: -------------------------------------------------------------------------------- 1 | /// 2 | "use strict"; 3 | 4 | var gulp = require("gulp"), 5 | rimraf = require("rimraf"), 6 | concat = require("gulp-concat"), 7 | cssmin = require("gulp-cssmin"), 8 | uglify = require("gulp-uglify"); 9 | 10 | var paths = { 11 | webroot: "./wwwroot/" 12 | }; 13 | 14 | paths.js = paths.webroot + "js/**/*.js"; 15 | paths.minJs = paths.webroot + "js/**/*.min.js"; 16 | paths.css = paths.webroot + "css/**/*.css"; 17 | paths.minCss = paths.webroot + "css/**/*.min.css"; 18 | paths.concatJsDest = paths.webroot + "js/site.min.js"; 19 | paths.concatCssDest = paths.webroot + "css/site.min.css"; 20 | 21 | gulp.task("clean:js", function (cb) { 22 | rimraf(paths.concatJsDest, cb); 23 | }); 24 | 25 | gulp.task("clean:css", function (cb) { 26 | rimraf(paths.concatCssDest, cb); 27 | }); 28 | 29 | gulp.task("clean", ["clean:js", "clean:css"]); 30 | 31 | gulp.task("min:js", function () { 32 | return gulp.src([paths.js, "!" + paths.minJs], { base: "." }) 33 | .pipe(concat(paths.concatJsDest)) 34 | .pipe(uglify()) 35 | .pipe(gulp.dest(".")); 36 | }); 37 | 38 | gulp.task("min:css", function () { 39 | return gulp.src([paths.css, "!" + paths.minCss]) 40 | .pipe(concat(paths.concatCssDest)) 41 | .pipe(cssmin()) 42 | .pipe(gulp.dest(".")); 43 | }); 44 | 45 | gulp.task("min", ["min:js", "min:css"]); 46 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ASP.NET", 3 | "version": "0.0.0", 4 | "devDependencies": { 5 | "gulp": "3.8.11", 6 | "gulp-concat": "2.5.2", 7 | "gulp-cssmin": "0.1.7", 8 | "gulp-uglify": "1.2.0", 9 | "rimraf": "2.2.8" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/simpleauthsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SimpleAuthSettings": { 3 | "EnablePasswordHasherUi": "true", 4 | "Comment-Recaptcha": "get your recaptcha keys here https://www.google.com/recaptcha/intro/index.html", 5 | "RecaptchaPublicKey": "", 6 | "RecaptchaPrivateKey": "", 7 | "AuthenticationScheme": "application" 8 | 9 | }, 10 | 11 | "Users": [ 12 | { 13 | "UserName": "admin", 14 | "Password": "admin", 15 | "PasswordIsHashed": "false", 16 | "Claims": [ 17 | { 18 | "ClaimType": "DisplayName", 19 | "ClaimValue": "Mr Cool!" 20 | }, 21 | { 22 | "ClaimType": "Email", 23 | "ClaimValue": "foo@foo.org" 24 | }, 25 | { 26 | "ClaimType": "Role", 27 | "ClaimValue": "Admins" 28 | } 29 | 30 | ] 31 | }, 32 | { 33 | "UserName": "demo", 34 | "Password": "demo", 35 | "PasswordIsHashed": "false", 36 | "Claims": [ 37 | { 38 | "ClaimType": "DisplayName", 39 | "ClaimValue": "Mr Awesome" 40 | }, 41 | { 42 | "ClaimType": "Email", 43 | "ClaimValue": "demo@demo.com" 44 | }, 45 | { 46 | "ClaimType": "Role", 47 | "ClaimValue": "Admins" 48 | } 49 | 50 | ] 51 | } 52 | ] 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption { 22 | z-index: 10 !important; 23 | } 24 | 25 | .carousel-caption p { 26 | font-size: 20px; 27 | line-height: 1.4; 28 | } 29 | 30 | @media (min-width: 768px) { 31 | .carousel-caption { 32 | z-index: 10 !important; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/basic.WebApp/src/basic.WebApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/images/ASP-NET-Banners-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/basic.WebApp/src/basic.WebApp/wwwroot/images/ASP-NET-Banners-01.png -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/images/ASP-NET-Banners-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/basic.WebApp/src/basic.WebApp/wwwroot/images/ASP-NET-Banners-02.png -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/images/Banner-01-Azure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/basic.WebApp/src/basic.WebApp/wwwroot/images/Banner-01-Azure.png -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/images/Banner-02-VS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/basic.WebApp/src/basic.WebApp/wwwroot/images/Banner-02-VS.png -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /samples/basic.WebApp/src/basic.WebApp/wwwroot/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/multiTenant.WebApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D2AD2392-AE84-46E2-9109-E2A9A1C93C26}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DD7BCE82-1945-4A3E-BC05-DCA3F64F65CF}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "multiTenant.WebApp", "src\multiTenant.WebApp\multiTenant.WebApp.csproj", "{93F114C9-C7F9-4BE1-AB18-E7A1E4F4F52F}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {93F114C9-C7F9-4BE1-AB18-E7A1E4F4F52F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {93F114C9-C7F9-4BE1-AB18-E7A1E4F4F52F}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {93F114C9-C7F9-4BE1-AB18-E7A1E4F4F52F}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {93F114C9-C7F9-4BE1-AB18-E7A1E4F4F52F}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | GlobalSection(NestedProjects) = preSolution 27 | {93F114C9-C7F9-4BE1-AB18-E7A1E4F4F52F} = {D2AD2392-AE84-46E2-9109-E2A9A1C93C26} 28 | EndGlobalSection 29 | EndGlobal 30 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/AppTenant.cs: -------------------------------------------------------------------------------- 1 |  2 | using cloudscribe.Web.SimpleAuth.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace multiTenant.WebApp 9 | { 10 | public class AppTenant 11 | { 12 | public string Id { get; set; } 13 | public string Name { get; set; } 14 | public string[] Hostnames { get; set; } 15 | public string Theme { get; set; } 16 | public string ConnectionString { get; set; } 17 | public List Users { get; set; } 18 | 19 | // overrides for default simpleauthsettings 20 | public bool EnablePasswordHasherUi { get; set; } = false; 21 | public string RecaptchaPublicKey { get; set; } 22 | public string RecaptchaPrivateKey { get; set; } 23 | public string AuthenticationScheme { get; set; } = "application"; 24 | 25 | public bool Equals(AppTenant other) 26 | { 27 | if (other == null) 28 | { 29 | return false; 30 | } 31 | 32 | return other.Name.Equals(Id); 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | return Equals(obj as AppTenant); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | return Id.GetHashCode(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/AppTenantAuthSettingsResolver.cs: -------------------------------------------------------------------------------- 1 | using cloudscribe.Web.SimpleAuth.Models; 2 | 3 | namespace multiTenant.WebApp 4 | { 5 | public class AppTenantAuthSettingsResolver : IAuthSettingsResolver 6 | { 7 | public AppTenantAuthSettingsResolver(AppTenant tenant) 8 | { 9 | this.tenant = tenant; 10 | 11 | authSettings = new SimpleAuthSettings(); 12 | authSettings.AuthenticationScheme = tenant.AuthenticationScheme; 13 | authSettings.RecaptchaPrivateKey = tenant.RecaptchaPrivateKey; 14 | authSettings.RecaptchaPublicKey = tenant.RecaptchaPublicKey; 15 | authSettings.EnablePasswordHasherUi = tenant.EnablePasswordHasherUi; 16 | } 17 | 18 | private AppTenant tenant; 19 | private SimpleAuthSettings authSettings; 20 | 21 | public SimpleAuthSettings GetCurrentAuthSettings() 22 | { 23 | return authSettings; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/AppTenantUserLookupProvider.cs: -------------------------------------------------------------------------------- 1 |  2 | using cloudscribe.Web.SimpleAuth.Models; 3 | 4 | namespace multiTenant.WebApp 5 | { 6 | public class AppTenantUserLookupProvider : IUserLookupProvider 7 | { 8 | public AppTenantUserLookupProvider(AppTenant tenant) 9 | { 10 | this.tenant = tenant; 11 | } 12 | 13 | private AppTenant tenant; 14 | 15 | public SimpleAuthUser GetUser(string userName) 16 | { 17 | foreach (SimpleAuthUser u in tenant.Users) 18 | { 19 | if (u.UserName == userName) { return u; } 20 | } 21 | 22 | return null; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/CachingAppTenantResolver.cs: -------------------------------------------------------------------------------- 1 |  2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.Caching.Memory; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using SaasKit.Multitenancy; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace multiTenant.WebApp 13 | { 14 | public class CachingAppTenantResolver : MemoryCacheTenantResolver 15 | { 16 | private readonly IEnumerable tenants; 17 | 18 | public CachingAppTenantResolver( 19 | IMemoryCache cache, 20 | ILoggerFactory loggerFactory, 21 | IOptions options) 22 | : base(cache, loggerFactory) 23 | { 24 | this.tenants = options.Value.Tenants; 25 | } 26 | 27 | protected override string GetContextIdentifier(HttpContext context) 28 | { 29 | return context.Request.Host.Value.ToLower(); 30 | } 31 | 32 | protected override IEnumerable GetTenantIdentifiers(TenantContext context) 33 | { 34 | return context.Tenant.Hostnames; 35 | } 36 | 37 | protected override Task> ResolveAsync(HttpContext context) 38 | { 39 | TenantContext tenantContext = null; 40 | 41 | var tenant = tenants.FirstOrDefault(t => 42 | t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower()))); 43 | 44 | if (tenant != null) 45 | { 46 | tenantContext = new TenantContext(tenant); 47 | } 48 | 49 | return Task.FromResult(tenantContext); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace multiTenant.WebApp.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | 16 | public IActionResult About() 17 | { 18 | ViewData["Message"] = "Your application description page."; 19 | 20 | return View(); 21 | } 22 | 23 | public IActionResult Contact() 24 | { 25 | ViewData["Message"] = "Your contact page."; 26 | 27 | return View(); 28 | } 29 | 30 | public IActionResult Error() 31 | { 32 | return View(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/MultiTenancyOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace multiTenant.WebApp 4 | { 5 | public class MultiTenancyOptions 6 | { 7 | public List Tenants { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52083/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "Hosting:Environment": "Development" 16 | } 17 | }, 18 | "web": { 19 | "commandName": "web", 20 | "environmentVariables": { 21 | "Hosting:Environment": "Development" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/ReadMe.md: -------------------------------------------------------------------------------- 1 | #Multi Tenant Demo instructions 2 | 3 | Note these instructions are not up to date and the startup code in this example is a bit different than it would typically be with a new project because this demo was created during the rc1 timeframe. 4 | A better sample can be found here: https://github.com/joeaudette/cloudscribe.StarterKits/tree/master/SimpleContent-SimpleAuth-nodb-multitenant/src/WebApp 5 | 6 | in windows explorer control-shift-right click the project folder "multiTenant.WebApp" and choose "open command windows here" 7 | 8 | you can test a simple tenant using the normal web command ie 9 | 10 | dnx web 11 | 12 | or you can test 2 tenants at once using 13 | 14 | dnx alltenents 15 | 16 | the command window will tell you the urls where the web app is listening 17 | 18 | open tenant 1 in your browser at 19 | http://localhost:60000 20 | 21 | open tenant 2 in your browser at 22 | http://localhost:60002 23 | 24 | tip: you can see the "alltenants" command in the project.json 25 | 26 | Note that these hosts only differ by ports for demo purposes but in production you would use different dns host names per tenant. 27 | 28 | 29 | Typically there would be a problem logging into 2 different sites using the same hostname "localhost", because the cookies for each tenant would have the same name and therefore logging into one tenant would also log in to another, ie the cookies would step on each other from each tenant. In this demo I used the [saaskit.MultiTenancy](https://github.com/saaskit/saaskit) which enables middleware branching per tenant so we could use different cookie names per tenant in the cookie middleware. You may or may not need different cookie settings per tenant in production, it depends on the specific needs of your project. 30 | 31 | The tenants and users are configured in the simpleauthsettings.json file 32 | There are 2 example tenants configured, read that file to find user names and passwords to login to the demo tenants 33 | 34 | Notes about tenants and this demo. 35 | 36 | this demo is just to show that there is a different tenant with different users and themes and how this can be easily implemented with SimpleAuth 37 | 38 | The "HomeController" in this demo does not produce different content per tenant. This is not a cms demo but since you could override any of the home views within a theme folder and since the content is directly in the views you can make different content per tenant that way if you want by using a different theme per tenant and adding different content in the theme specific views. 39 | 40 | by default views come from below the normal Views folder 41 | 42 | you can override any normal view by copying it up to the corresponding location in the theme folder and then modify it if needed. 43 | you don't have to override any views unless you need to 44 | the Darkly theme has an example overriding the Homes/About.cshtml 45 | 46 | typically a custom theme must at elast override the _Layout.cshtml file in order to bring in custom css 47 | 48 | only files below wwwroot can be served over https requests, therefore the css cannot be inside the theme folders 49 | so there is also a folder wwwroot/css/themes and each theme links to its own css from the _Layout.cshtml file 50 | 51 | Multi tenancy per se is not a feature of SimpleAuth this demo is to show that you can use SimpleAuth in mutli tenant scenarios easily. 52 | It uses Saaskit.MultiTenancy and a few classes implemented directly in the multiTenant.WebApp 53 | which you could use and modify as part of your own app if you want to. 54 | 55 | See the SimpleAuth Readme to understand the limitations of SimpleAuth 56 | https://github.com/joeaudette/cloudscribe.Web.SimpleAuth 57 | 58 | even in multi tenant scenarios SimpleAuth is only intended for small numbers of users 59 | I would use for cases if I have a few personal web sites I can run them as tenants if they otherwise use the same software components 60 | ie I could build a content system that is tenant aware and run multiple small sites from a single installation using this approach 61 | 62 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Startup.cs: -------------------------------------------------------------------------------- 1 | using cloudscribe.Web.SimpleAuth.Models; 2 | using cloudscribe.Web.SimpleAuth.Services; 3 | using Microsoft.AspNetCore.Authentication.Cookies; 4 | using System.Collections.Generic; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Mvc.Razor; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Logging; 13 | using System.IO; 14 | using Microsoft.Extensions.Options; 15 | 16 | namespace multiTenant.WebApp 17 | { 18 | public class Startup 19 | { 20 | public Startup(IHostingEnvironment env) 21 | { 22 | // Set up configuration sources. 23 | var builder = new ConfigurationBuilder() 24 | .AddJsonFile("appsettings.json") 25 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); 26 | 27 | // you can use whatever file name you like and it is probably a good idea to use a custom file name 28 | // just an a small extra protection in case hackers try some kind of attack based on knowing the name of the file 29 | // it should not be possible for anyone to get files outside of wwwroot using http requests 30 | // but every little thing you can do for stronger security is a good idea 31 | builder.AddJsonFile("simpleauthsettings.json", optional: true); 32 | 33 | if (env.IsDevelopment()) 34 | { 35 | // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709 36 | builder.AddUserSecrets(); 37 | } 38 | 39 | builder.AddEnvironmentVariables(); 40 | Configuration = builder.Build(); 41 | } 42 | 43 | public IConfigurationRoot Configuration { get; set; } 44 | 45 | // This method gets called by the runtime. Use this method to add services to the container. 46 | public void ConfigureServices(IServiceCollection services) 47 | { 48 | services.Configure(Configuration.GetSection("MultiTenancy")); 49 | services.AddMultitenancy(); 50 | services.AddCloudscribeSimpleAuth(); 51 | 52 | services.AddScoped(); 53 | services.Configure>(Configuration.GetSection("Users")); 54 | services.AddScoped, PasswordHasher>(); 55 | services.AddScoped(); 56 | services.AddScoped(); 57 | 58 | 59 | services.AddMvc(); 60 | services.Configure(options => 61 | { 62 | options.ViewLocationExpanders.Add(new TenantViewLocationExpander()); 63 | }); 64 | 65 | 66 | } 67 | 68 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 69 | public void Configure( 70 | IApplicationBuilder app, 71 | IHostingEnvironment env, 72 | ILoggerFactory loggerFactory) 73 | { 74 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 75 | loggerFactory.AddDebug(); 76 | 77 | if (env.IsDevelopment()) 78 | { 79 | app.UseBrowserLink(); 80 | app.UseDeveloperExceptionPage(); 81 | } 82 | else 83 | { 84 | app.UseExceptionHandler("/Home/Error"); 85 | 86 | 87 | } 88 | 89 | app.UseStaticFiles(); 90 | 91 | app.UseMultitenancy(); 92 | app.UsePerTenant((ctx, builder) => 93 | { 94 | var ApplicationCookie = new CookieAuthenticationOptions 95 | { 96 | AuthenticationScheme = ctx.Tenant.AuthenticationScheme, 97 | LoginPath = new PathString("/login/index"), 98 | AccessDeniedPath = new PathString("/"), 99 | AutomaticAuthenticate = true, 100 | AutomaticChallenge = true, 101 | CookieName = $"{ctx.Tenant.Id}.application" 102 | }; 103 | builder.UseCookieAuthentication(ApplicationCookie); 104 | }); 105 | 106 | 107 | 108 | 109 | app.UseMvc(routes => 110 | { 111 | routes.MapRoute( 112 | name: "default", 113 | template: "{controller=Home}/{action=Index}/{id?}"); 114 | }); 115 | } 116 | 117 | // Entry point for the application. 118 | public static void Main(string[] args) 119 | { 120 | var host = new WebHostBuilder() 121 | .UseKestrel() 122 | .UseContentRoot(Directory.GetCurrentDirectory()) 123 | .UseIISIntegration() 124 | .UseStartup() 125 | .Build(); 126 | 127 | host.Run(); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/TenantViewLocationExpander.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace multiTenant.WebApp 9 | { 10 | public class TenantViewLocationExpander : IViewLocationExpander 11 | { 12 | private const string THEME_KEY = "theme", TENANT_KEY = "tenant"; 13 | 14 | public void PopulateValues(ViewLocationExpanderContext context) 15 | { 16 | context.Values[THEME_KEY] 17 | = context.ActionContext.HttpContext.GetTenant()?.Theme; 18 | 19 | context.Values[TENANT_KEY] 20 | = context.ActionContext.HttpContext.GetTenant()?.Name.Replace(" ", "-"); 21 | } 22 | 23 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) 24 | { 25 | string theme = null; 26 | if (context.Values.TryGetValue(THEME_KEY, out theme)) 27 | { 28 | IEnumerable themeLocations = new[] 29 | { 30 | $"/Themes/{theme}/{{1}}/{{0}}.cshtml", 31 | $"/Themes/{theme}/Shared/{{0}}.cshtml" 32 | }; 33 | 34 | string tenant; 35 | if (context.Values.TryGetValue(TENANT_KEY, out tenant)) 36 | { 37 | themeLocations = ExpandTenantLocations(tenant, themeLocations); 38 | } 39 | 40 | viewLocations = themeLocations.Concat(viewLocations); 41 | } 42 | 43 | 44 | return viewLocations; 45 | } 46 | 47 | private IEnumerable ExpandTenantLocations(string tenant, IEnumerable defaultLocations) 48 | { 49 | foreach (var location in defaultLocations) 50 | { 51 | yield return location.Replace("{0}", $"{{0}}_{tenant}"); 52 | yield return location; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Cerulean/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Cerulean/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Cerulean/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 67 | 68 | 111 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Cerulean/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject AppTenant Tenant 2 | 3 | 4 | 5 | 6 | 7 | @ViewData["Title"] - @(Tenant?.Name ?? "Sample") 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 45 |
46 | @RenderBody() 47 |
48 |
49 |

© 2016 - SaasKit Sample Cerulean theme

50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 67 | 68 | 69 | 70 | @RenderSection("scripts", required: false) 71 | 72 | 73 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Darkly/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

This is the Darkly theme.

8 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Darkly/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |
8 | One Darkly Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@darkly.com
16 | Marketing: Marketing@darkly.com 17 |
18 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Darkly/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 6 |
7 | 8 |
9 |
10 |

Welcome to Darkly Industries

11 |
12 | 13 |

14 | 15 |

16 |

17 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut tempus, libero eget feugiat consectetur, risus nunc varius arcu, rutrum bibendum ex lectus ac mauris. Vivamus non tortor at metus mollis congue quis sed nunc. Maecenas lacinia sagittis rhoncus. Proin eros tortor, ultricies vitae neque at, rhoncus feugiat ipsum. Nunc commodo ullamcorper posuere. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean consectetur, lorem ac porta porttitor, turpis turpis faucibus lectus, ac auctor massa ligula sit amet libero. Aenean lobortis viverra diam. Sed id venenatis nulla. Sed sodales volutpat volutpat. Phasellus accumsan ut velit non consequat. Mauris commodo, nibh at consequat euismod, orci augue euismod metus, ac maximus mauris tellus sed lectus. Aliquam ultrices porta turpis, ac porta augue congue maximus. Nam dignissim fermentum arcu in posuere. Quisque quis malesuada justo. Nam scelerisque, magna et feugiat tincidunt, lacus orci porta libero, quis mollis dolor augue id diam. 18 | 19 | Vivamus ultricies consequat erat, ac luctus dolor fermentum sed. Fusce quis lacus eget risus tempus interdum et luctus odio. Mauris sed pharetra dolor, id feugiat arcu. Phasellus nibh turpis, vehicula in lorem in, porttitor vulputate justo. Integer sit amet libero elit. Vivamus elit enim, congue at blandit ut, tempus tristique ipsum. Phasellus aliquam libero ac odio elementum, a porttitor orci tincidunt. Curabitur nec commodo ex. Pellentesque mauris elit, pellentesque nec elementum eget, molestie a sem. In hac habitasse platea dictumst. 20 | 21 | Vivamus at neque euismod, interdum odio vel, hendrerit justo. Donec efficitur sagittis enim, a cursus libero mattis vel. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus iaculis nunc eu ornare tincidunt. Cras ligula sem, viverra ut neque at, blandit commodo risus. Fusce malesuada efficitur elit vel imperdiet. Integer dignissim nisl in urna varius, in porttitor nisi luctus. 22 | 23 | Ut rutrum, lorem at accumsan vehicula, arcu justo dictum ipsum, a placerat augue urna at ante. Nunc ac finibus erat. Ut fermentum, justo et pharetra faucibus, odio orci aliquam dui, vitae fermentum purus ante eget dui. Curabitur iaculis, mi nec dictum semper, est lectus lobortis orci, eu luctus purus tellus at metus. Aenean id placerat orci. Quisque vitae justo viverra, varius urna viverra, ullamcorper ipsum. Nullam bibendum molestie viverra. Nulla facilisi. Ut venenatis ante vel risus euismod, id convallis erat condimentum. Quisque id metus sollicitudin, tincidunt tortor eu, auctor ligula. In a felis dui. Nunc quis sem ut tortor condimentum ultrices. Proin sagittis blandit velit sit amet imperdiet. Praesent suscipit libero laoreet porttitor fringilla. Sed a iaculis purus. 24 | 25 | Donec non magna in augue accumsan lobortis. Quisque convallis lorem ut massa accumsan cursus. Aenean consectetur eleifend porta. Pellentesque ac convallis ante. Quisque enim odio, lobortis a posuere in, blandit ac arcu. Integer et congue tellus, vitae placerat velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi tincidunt est molestie, fringilla eros ac, vestibulum mi. Maecenas pharetra diam aliquam purus commodo, lacinia lacinia erat blandit. Donec sed luctus dui, sed dignissim diam. 26 |

27 |
28 |
29 |

Application uses

30 |
    31 |
  • Sample pages using ASP.NET Core
  • 32 |
  • Gulp and Bower for managing client-side libraries
  • 33 |
  • Theming using Bootstrap
  • 34 |
35 |
36 |
37 |

Overview

38 | 39 |
40 | 41 |
42 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/Darkly/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject AppTenant Tenant 2 | 3 | 4 | 5 | 6 | 7 | @ViewData["Title"] - @(Tenant?.Name ?? "Sample") 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 45 |
46 | @RenderBody() 47 |
48 |
49 |

© 2016 - SaasKit Sample

50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 67 | 68 | 69 | 70 | @RenderSection("scripts", required: false) 71 | 72 | 73 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using multiTenant.WebApp 2 | @using Microsoft.AspNetCore.Http; 3 | @using SaasKit.Multitenancy; 4 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 5 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Themes/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 67 | 68 | 111 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Login/HashPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model cloudscribe.Web.SimpleAuth.ViewModels.HashPasswordViewModel 2 | 3 | 4 | @{ 5 | ViewData["Title"] = "Password Hasher"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | 11 |

Hash Password Form

12 |

13 | Simple auth user account information is stored in a simple json file. You could define users with clear text passwords, 14 | but that means anyone who can access the file, such as employees at your web host etc, could access your password. 15 | It would be better to store the passwords as hashed since it would be extremely difficult if not impossible for someone to 16 | determine the password from the hash. This utility uses the ASP.NET Identity PasswordHasher to generate the hashes. 17 | Hashes are one way encryption, they are designed to be impossible to decrypt. 18 | When you login, the password you enter is hashed and validated against the hash in storage, and if it matches authorization is granted. 19 |

20 |

21 | Note however, that if you enter the same password into the hash generator, a different hash will be generated each time, so it is not possible 22 | to just try generating hashes to see if it matches a hash that has been leaked. This makes it very hard for a hacker to get your password from 23 | the hash even if he manages to find out the hash. There is a complex algorithm with 24 | salt added to make sure the hashes are not deterministic. 25 |

26 |

27 | Keep in mind that use of this utility should only be done either from a localhost installation, or from an SSL/TLS protected endpoint. 28 | Don't hash your password while using public wifi. 29 |

30 |

31 | Don't use a password that is the same or following any pattern similar to your other passwords at other web sites. 32 | If you do that and a hacker manages to get your password from any site, he can try to guess passwords for your other website accounts. 33 |

34 |

35 | Be sure to use a password of at least 8 characters, more is better, and use upper and lower case letters as well as numbers and 36 | special characters to make sure you start with a strong password. Don't use dictionary words. We are not masking the password here so make sure no-one is 37 | shoulder surfing when you create your password hash. 38 |

39 |

40 | Once all your users have generated their hashes and their hashes have been added to the simpleauthsettings.json file, you probably should disable this page by setting 41 | EnablePasswordHasherUi as false. 42 | 43 |

44 |
45 |
46 |
47 | 48 |
49 | 50 | 51 |
52 |
53 | @if (Model.OutputHash.Length > 0) 54 | { 55 | 56 |
57 | 58 |
59 | 60 | 61 |
62 |
63 | 64 | } 65 | 66 | 67 |
68 |
69 | 70 |
71 |
72 |
73 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Login/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Collections.Generic 2 | @using Microsoft.AspNetCore.Http 3 | @using Microsoft.AspNetCore.Http.Authentication 4 | @model cloudscribe.Web.SimpleAuth.ViewModels.LoginViewModel 5 | 6 | @if (!string.IsNullOrEmpty(Model.RecaptchaSiteKey)) 7 | { 8 | 9 | } 10 | 11 | @{ 12 | ViewData["Title"] = "Log in"; 13 | } 14 | 15 |

@ViewData["Title"].

16 |
17 |
18 |
19 |
20 |

Use a local account to log in.

21 |
22 |
23 |
24 | 25 |
26 | 27 | 28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 |
43 |
44 |
45 | @if (Model.RecaptchaSiteKey.Length > 0) 46 | { 47 |
48 |
49 |
50 | @Html.ValidationMessage("recaptchaerror", new { @class = "text-danger" }) 51 |
52 |
53 | 54 | } 55 |
56 |
57 | 58 |
59 |
60 | 61 |
62 |
63 |
64 |
65 | 66 | @section Scripts { 67 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 68 | } 69 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - multiTenant.WebApp 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 41 |
42 | @RenderBody() 43 |
44 |
45 |

© 2016 - multiTenant.WebApp

46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 63 | 64 | 65 | 66 | @RenderSection("scripts", required: false) 67 | 68 | 69 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Security.Claims 2 | @using cloudscribe.Web.SimpleAuth.Extensions 3 | 4 | @if (User.Identity.IsAuthenticated) 5 | { 6 | 14 | } 15 | else 16 | { 17 | 20 | } 21 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using multiTenant.WebApp 2 | @using Microsoft.AspNetCore.Identity 3 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 4 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Data": { 3 | "DefaultConnection": { 4 | "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-multiTenant.WebApp-1662b592-8e49-4786-a59a-f593c17d8572;Trusted_Connection=True;MultipleActiveResultSets=true" 5 | } 6 | }, 7 | "Logging": { 8 | "IncludeScopes": false, 9 | "LogLevel": { 10 | "Default": "Debug", 11 | "System": "Information", 12 | "Microsoft": "Information" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ASP.NET", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.5", 6 | "jquery": "2.1.4", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.4" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/gulpfile.js: -------------------------------------------------------------------------------- 1 | /// 2 | "use strict"; 3 | 4 | var gulp = require("gulp"), 5 | rimraf = require("rimraf"), 6 | concat = require("gulp-concat"), 7 | cssmin = require("gulp-cssmin"), 8 | uglify = require("gulp-uglify"); 9 | 10 | var paths = { 11 | webroot: "./wwwroot/" 12 | }; 13 | 14 | paths.js = paths.webroot + "js/**/*.js"; 15 | paths.minJs = paths.webroot + "js/**/*.min.js"; 16 | paths.css = paths.webroot + "css/**/*.css"; 17 | paths.minCss = paths.webroot + "css/**/*.min.css"; 18 | paths.concatJsDest = paths.webroot + "js/site.min.js"; 19 | paths.concatCssDest = paths.webroot + "css/site.min.css"; 20 | 21 | gulp.task("clean:js", function (cb) { 22 | rimraf(paths.concatJsDest, cb); 23 | }); 24 | 25 | gulp.task("clean:css", function (cb) { 26 | rimraf(paths.concatCssDest, cb); 27 | }); 28 | 29 | gulp.task("clean", ["clean:js", "clean:css"]); 30 | 31 | gulp.task("min:js", function () { 32 | return gulp.src([paths.js, "!" + paths.minJs], { base: "." }) 33 | .pipe(concat(paths.concatJsDest)) 34 | .pipe(uglify()) 35 | .pipe(gulp.dest(".")); 36 | }); 37 | 38 | gulp.task("min:css", function () { 39 | return gulp.src([paths.css, "!" + paths.minCss]) 40 | .pipe(concat(paths.concatCssDest)) 41 | .pipe(cssmin()) 42 | .pipe(gulp.dest(".")); 43 | }); 44 | 45 | gulp.task("min", ["min:js", "min:css"]); 46 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/multiTenant.WebApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1 5 | multiTenant.WebApp 6 | multiTenant.WebApp 7 | aspnet5-multiTenant.WebApp-1662b592-8e49-4786-a59a-f593c17d8572 8 | 9 | 10 | 11 | $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ASP.NET", 3 | "version": "0.0.0", 4 | "devDependencies": { 5 | "gulp": "3.8.11", 6 | "gulp-concat": "2.5.2", 7 | "gulp-cssmin": "0.1.7", 8 | "gulp-uglify": "1.2.0", 9 | "rimraf": "2.2.8" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/simpleauthsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "MultiTenancy": { 4 | "Tenants": [ 5 | { 6 | "Id": "tenant1", 7 | "Name": "sample site", 8 | "Hostnames": [ 9 | "localhost:60000", 10 | "localhost:52083" 11 | ], 12 | "Theme": "Cerulean", 13 | "EnablePasswordHasherUi": "true", 14 | "Comment-Recaptcha": "get your recaptcha keys here https://www.google.com/recaptcha/intro/index.html", 15 | "RecaptchaPublicKey": "", 16 | "RecaptchaPrivateKey": "", 17 | "AuthenticationScheme": "application", 18 | "Users": [ 19 | { 20 | "UserName": "admin", 21 | "FYI-Comment": "this hash is a hash of the word admin, to create your own password hash, set EnablePasswordHasherUi as true, and visit /Login/HashPassword", 22 | "Password": "AQAAAAEAACcQAAAAEKyu1zRJBNHJUw4r1mJwxgHfvofBmIAZSiG8X1O8dmdBFiQT0183/oqeoo0qGHv/Hw==", 23 | "PasswordIsHashed": "true", 24 | "Claims": [ 25 | { 26 | "ClaimType": "DisplayName", 27 | "ClaimValue": "Rock Star" 28 | }, 29 | { 30 | "ClaimType": "Email", 31 | "ClaimValue": "demo@demo.com" 32 | }, 33 | { 34 | "ClaimType": "Role", 35 | "ClaimValue": "Admins" 36 | } 37 | 38 | ] 39 | }, 40 | { 41 | "UserName": "member", 42 | "Password": "member", 43 | "PasswordIsHashed": "false", 44 | "Claims": [ 45 | { 46 | "ClaimType": "DisplayName", 47 | "ClaimValue": "Mr Awesome" 48 | }, 49 | { 50 | "ClaimType": "Email", 51 | "ClaimValue": "demo2@demo.com" 52 | }, 53 | { 54 | "ClaimType": "Role", 55 | "ClaimValue": "Members" 56 | } 57 | 58 | ] 59 | } 60 | ] 61 | }, 62 | { 63 | "Id": "tenant2", 64 | "Name": "darkly industries", 65 | "Hostnames": [ 66 | "localhost:60002" 67 | ], 68 | "Theme": "Darkly", 69 | "EnablePasswordHasherUi": "true", 70 | "RecaptchaPublicKey": "", 71 | "RecaptchaPrivateKey": "", 72 | "AuthenticationScheme": "application", 73 | "Users": [ 74 | { 75 | "UserName": "admin", 76 | "FYI-Comment": "this hash is a hash of the word admin, to create your own password hash, set EnablePasswordHasherUi as true, and visit /Login/HashPassword", 77 | "Password": "AQAAAAEAACcQAAAAEKyu1zRJBNHJUw4r1mJwxgHfvofBmIAZSiG8X1O8dmdBFiQT0183/oqeoo0qGHv/Hw==", 78 | "PasswordIsHashed": "true", 79 | "Claims": [ 80 | { 81 | "ClaimType": "DisplayName", 82 | "ClaimValue": "Rock Star Darkly" 83 | }, 84 | { 85 | "ClaimType": "Email", 86 | "ClaimValue": "demo@demo.com" 87 | }, 88 | { 89 | "ClaimType": "Role", 90 | "ClaimValue": "Admins" 91 | } 92 | 93 | ] 94 | }, 95 | { 96 | "UserName": "member", 97 | "Password": "member", 98 | "PasswordIsHashed": "false", 99 | "Claims": [ 100 | { 101 | "ClaimType": "DisplayName", 102 | "ClaimValue": "Mr Awesome Darkly" 103 | }, 104 | { 105 | "ClaimType": "Email", 106 | "ClaimValue": "demo2@demo.com" 107 | }, 108 | { 109 | "ClaimType": "Role", 110 | "ClaimValue": "Members" 111 | } 112 | 113 | ] 114 | } 115 | ] 116 | } 117 | ] 118 | } 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption { 22 | z-index: 10 !important; 23 | } 24 | 25 | .carousel-caption p { 26 | font-size: 20px; 27 | line-height: 1.4; 28 | } 29 | 30 | @media (min-width: 768px) { 31 | .carousel-caption { 32 | z-index: 10 !important; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/ASP-NET-Banners-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/ASP-NET-Banners-01.png -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/ASP-NET-Banners-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/ASP-NET-Banners-02.png -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/Banner-01-Azure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/Banner-01-Azure.png -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/Banner-02-VS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/images/Banner-02-VS.png -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /samples/multiTenant.WebApp/src/multiTenant.WebApp/wwwroot/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/ClaimsPrincipalExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Author: Joe Audette 4 | // Created: 2016-02-09 5 | // Last Modified: 2016-02-20 6 | // 7 | 8 | using System; 9 | using System.Security.Claims; 10 | 11 | namespace cloudscribe.Web.SimpleAuth.Extensions 12 | { 13 | public static class ClaimsPrincipalExtensions 14 | { 15 | public static string GetDisplayName(this ClaimsPrincipal principal) 16 | { 17 | if (principal == null) 18 | { 19 | throw new ArgumentNullException(nameof(principal)); 20 | } 21 | var claim = principal.FindFirst("DisplayName"); 22 | return claim != null ? claim.Value : null; 23 | } 24 | 25 | public static string GetEmail(this ClaimsPrincipal principal) 26 | { 27 | if (principal == null) 28 | { 29 | throw new ArgumentNullException(nameof(principal)); 30 | } 31 | var claim = principal.FindFirst(ClaimTypes.Email); 32 | return claim != null ? claim.Value : null; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Models/IAuthSettingsResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace cloudscribe.Web.SimpleAuth.Models 7 | { 8 | public interface IAuthSettingsResolver 9 | { 10 | SimpleAuthSettings GetCurrentAuthSettings(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Models/IUserLookupProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace cloudscribe.Web.SimpleAuth.Models 7 | { 8 | public interface IUserLookupProvider 9 | { 10 | SimpleAuthUser GetUser(string userName); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Models/RecaptchaResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Author: Joe Audette 4 | // Created: 2015-05-20 5 | // Last Modified: 2015-05-20 6 | // 7 | 8 | using Newtonsoft.Json; 9 | using System.Collections.Generic; 10 | 11 | namespace cloudscribe.Web.SimpleAuth.Models 12 | { 13 | public class RecaptchaResponse 14 | { 15 | [JsonProperty("success")] 16 | public bool Success { get; set; } 17 | 18 | [JsonProperty("error-codes")] 19 | public List ErrorCodes { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Models/SimpleAuthClaim.cs: -------------------------------------------------------------------------------- 1 | namespace cloudscribe.Web.SimpleAuth.Models 2 | { 3 | public class SimpleAuthClaim 4 | { 5 | public string ClaimType { get; set; } 6 | public string ClaimValue { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Models/SimpleAuthSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Author: Joe Audette 4 | // Created: 2016-02-09 5 | // Last Modified: 2016-02-09 6 | // 7 | 8 | 9 | using System.Collections.Generic; 10 | 11 | namespace cloudscribe.Web.SimpleAuth.Models 12 | { 13 | public class SimpleAuthSettings 14 | { 15 | //public List Users { get; set; } 16 | 17 | /// 18 | /// if true the /Login/Hasher will be available to use for generating a hash, 19 | /// that can then be stored in settings instead of clear textpassword 20 | /// 21 | public bool EnablePasswordHasherUi { get; set; } = false; 22 | 23 | public string RecaptchaPublicKey { get; set; } 24 | public string RecaptchaPrivateKey { get; set; } 25 | public string AuthenticationScheme { get; set; } = "application"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Models/SimpleAuthUser.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Author: Joe Audette 4 | // Created: 2016-02-09 5 | // Last Modified: 2016-02-09 6 | // 7 | 8 | using System.Collections.Generic; 9 | 10 | namespace cloudscribe.Web.SimpleAuth.Models 11 | { 12 | public class SimpleAuthUser 13 | { 14 | 15 | 16 | public string UserName { get; set; } 17 | public string Password { get; set; } 18 | public bool PasswordIsHashed { get; set; } 19 | public List Claims { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("cloudscribe.Web.SimpleAuth")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("754228fb-2122-4d86-81eb-774d7e713b9f")] 20 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using cloudscribe.Web.SimpleAuth.Controllers; 2 | using cloudscribe.Web.SimpleAuth.Models; 3 | using cloudscribe.Web.SimpleAuth.Services; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc.Razor; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | using Microsoft.Extensions.FileProviders; 9 | using System.Reflection; 10 | 11 | namespace Microsoft.Extensions.DependencyInjection 12 | { 13 | public static class ServiceCollectionExtensions 14 | { 15 | public static IServiceCollection AddCloudscribeSimpleAuth( 16 | this IServiceCollection services 17 | ) 18 | { 19 | services.TryAddSingleton(); 20 | services.TryAddScoped(); // single tenant 21 | services.TryAddScoped, PasswordHasher>(); 22 | services.TryAddScoped(); 23 | services.AddScoped(); 24 | 25 | return services; 26 | } 27 | 28 | /// 29 | /// This method adds an embedded file provider to the RazorViewOptions to be able to load the SimpleAuth related views. 30 | /// If you download and install the views below your view folder you don't need this method and you can customize the views. 31 | /// You can get the views from https://github.com/joeaudette/cloudscribe.Web.SimpleAuth/tree/master/src/cloudscribe.Web.SimpleAuth/Views 32 | /// 33 | /// 34 | /// RazorViewEngineOptions 35 | public static RazorViewEngineOptions AddEmbeddedViewsForSimpleAuth(this RazorViewEngineOptions options) 36 | { 37 | options.FileProviders.Add(new EmbeddedFileProvider( 38 | typeof(LoginController).GetTypeInfo().Assembly, 39 | "cloudscribe.Web.SimpleAuth" 40 | )); 41 | 42 | return options; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Services/DefaultAuthSettingsResolver.cs: -------------------------------------------------------------------------------- 1 |  2 | using cloudscribe.Web.SimpleAuth.Models; 3 | using Microsoft.Extensions.Options; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace cloudscribe.Web.SimpleAuth.Services 10 | { 11 | public class DefaultAuthSettingsResolver : IAuthSettingsResolver 12 | { 13 | public DefaultAuthSettingsResolver(IOptions settingsAccessor) 14 | { 15 | authSettings = settingsAccessor.Value; 16 | } 17 | 18 | private SimpleAuthSettings authSettings; 19 | 20 | public SimpleAuthSettings GetCurrentAuthSettings() 21 | { 22 | return authSettings; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Services/DefaultUserLookupProvider.cs: -------------------------------------------------------------------------------- 1 |  2 | using cloudscribe.Web.SimpleAuth.Models; 3 | using Microsoft.Extensions.Options; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace cloudscribe.Web.SimpleAuth.Services 10 | { 11 | public class DefaultUserLookupProvider : IUserLookupProvider 12 | { 13 | public DefaultUserLookupProvider(IOptions> usersAccessor) 14 | { 15 | allUsers = usersAccessor.Value; 16 | } 17 | 18 | private List allUsers; 19 | 20 | public SimpleAuthUser GetUser(string userName) 21 | { 22 | foreach (SimpleAuthUser u in allUsers) 23 | { 24 | if (u.UserName == userName) { return u; } 25 | } 26 | 27 | return null; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Services/SignInManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Author: Joe Audette 4 | // Created: 2016-02-15 5 | // Last Modified: 2016-02-20 6 | // 7 | 8 | using cloudscribe.Web.SimpleAuth.Models; 9 | using Microsoft.AspNetCore.Identity; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using System.Collections.Generic; 13 | using System.Security.Claims; 14 | 15 | namespace cloudscribe.Web.SimpleAuth.Services 16 | { 17 | public class SignInManager 18 | { 19 | public SignInManager( 20 | IAuthSettingsResolver settingsResolver, 21 | IUserLookupProvider userLookupProvider, 22 | IPasswordHasher passwordHasher, 23 | ILogger logger) 24 | { 25 | authSettings = settingsResolver.GetCurrentAuthSettings(); 26 | userRepo = userLookupProvider; 27 | this.passwordHasher = passwordHasher; 28 | log = logger; 29 | } 30 | 31 | private IUserLookupProvider userRepo; 32 | private SimpleAuthSettings authSettings; 33 | private IPasswordHasher passwordHasher; 34 | //private List allUsers; 35 | private ILogger log; 36 | 37 | public SimpleAuthSettings AuthSettings 38 | { 39 | get { return authSettings; } 40 | } 41 | 42 | public SimpleAuthUser GetUser(string userName) 43 | { 44 | return userRepo.GetUser(userName); 45 | } 46 | 47 | public bool ValidatePassword(SimpleAuthUser authUser, string providedPassword) 48 | { 49 | bool result = false; 50 | if (authUser.PasswordIsHashed) 51 | { 52 | var hashResult = passwordHasher.VerifyHashedPassword(authUser, authUser.Password, providedPassword); 53 | result = (hashResult == PasswordVerificationResult.Success); 54 | 55 | } 56 | else 57 | { 58 | result = authUser.Password == providedPassword; 59 | } 60 | 61 | return result; 62 | } 63 | 64 | public ClaimsPrincipal GetClaimsPrincipal(SimpleAuthUser authUser) 65 | { 66 | var identity = new ClaimsIdentity(authSettings.AuthenticationScheme); 67 | identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "1")); 68 | identity.AddClaim(new Claim(ClaimTypes.Name, authUser.UserName)); 69 | 70 | foreach (SimpleAuthClaim c in authUser.Claims) 71 | { 72 | if (c.ClaimType == "Email") 73 | { 74 | identity.AddClaim(new Claim(ClaimTypes.Email, c.ClaimValue)); 75 | } 76 | else if (c.ClaimType == "Role") 77 | { 78 | identity.AddClaim(new Claim(ClaimTypes.Role, c.ClaimValue)); 79 | } 80 | else 81 | { 82 | identity.AddClaim(new Claim(c.ClaimType, c.ClaimValue)); 83 | } 84 | } 85 | 86 | var prince = new ClaimsPrincipal(identity); 87 | 88 | return prince; 89 | } 90 | 91 | public string HashPassword(string inputPassword) 92 | { 93 | var fakeUser = new SimpleAuthUser(); 94 | return passwordHasher.HashPassword(fakeUser, inputPassword); 95 | } 96 | 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/ViewModels/HashPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Author: Joe Audette 4 | // Created: 2016-02-10 5 | // Last Modified: 2016-02-10 6 | // 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.ComponentModel.DataAnnotations; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | 14 | namespace cloudscribe.Web.SimpleAuth.ViewModels 15 | { 16 | public class HashPasswordViewModel 17 | { 18 | [Required] 19 | [Display(Name = "Input Password")] 20 | public string InputPassword { get; set; } = string.Empty; 21 | 22 | [Display(Name = "Generated Hash")] 23 | public string OutputHash { get; set; } = string.Empty; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/ViewModels/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Author: Joe Audette 4 | // Created: 2016-02-09 5 | // Last Modified: 2016-02-09 6 | // 7 | 8 | using System.ComponentModel.DataAnnotations; 9 | 10 | namespace cloudscribe.Web.SimpleAuth.ViewModels 11 | { 12 | public class LoginViewModel 13 | { 14 | 15 | [Required] 16 | [Display(Name = "User Name")] 17 | public string UserName { get; set; } 18 | 19 | [Required] 20 | [DataType(DataType.Password)] 21 | [Display(Name = "Password")] 22 | public string Password { get; set; } 23 | 24 | [Display(Name = "Remember me?")] 25 | public bool RememberMe { get; set; } 26 | 27 | public string RecaptchaSiteKey { get; set; } = string.Empty; 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Views/Login/HashPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model cloudscribe.Web.SimpleAuth.ViewModels.HashPasswordViewModel 2 | 3 | 4 | @{ 5 | ViewData["Title"] = "Password Hasher"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | 11 |

Hash Password Form

12 |

13 | Simple auth user account information is stored in a simple json file. You could define users with clear text passwords, 14 | but that means anyone who can access the file, such as employees at your web host etc, could access your password. 15 | It would be better to store the passwords as hashed since it would be extremely difficult if not impossible for someone to 16 | determine the password from the hash. This utility uses the ASP.NET Identity PasswordHasher to generate the hashes. 17 | Hashes are one way encryption, they are designed to be impossible to decrypt. 18 | When you login, the password you enter is hashed and validated against the hash in storage, and if it matches authorization is granted. 19 |

20 |

21 | Note however, that if you enter the same password into the hash generator, a different hash will be generated each time, so it is not possible 22 | to just try generating hashes to see if it matches a hash that has been leaked. This makes it very hard for a hacker to get your password from 23 | the hash even if he manages to find out the hash. There is a complex algorythm with 24 | salt added to make sure the hashes are not deterministic. 25 |

26 |

27 | Keep in mind that use of this utility should only be done either from a localhost installation, or from an SSL/TLS protected endpoint. 28 | Don't hash your password while using public wifi. 29 |

30 |

31 | Don't use a password that is the same or following any pattern similar to your other passwords at other web sites. 32 | If you do that and a hacker manages to get your password from any site, he can try to guess passwords for your other website accounts. 33 |

34 |

35 | Be sure to use a password of at least 8 characters, more is better, and use upper and lower case letters as well as numbers and 36 | special characters to make sure you start with a strong password. Don't use dictionary words. We are not masking the password here so make sure no-one is 37 | shoulder surfing when you create your password hash. 38 |

39 |

40 | Once all your users have generated their hashes and their hashes have been added to the simpleauthsettings.json file, you probably should disable this page by setting 41 | EnablePasswordHasherUi as false. 42 | 43 |

44 |
45 |
46 |
47 | 48 |
49 | 50 | 51 |
52 |
53 | @if (Model.OutputHash.Length > 0) 54 | { 55 | 56 |
57 | 58 |
59 | 60 | 61 |
62 |
63 | 64 | } 65 | 66 | 67 |
68 |
69 | 70 |
71 |
72 |
-------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Views/Login/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Collections.Generic 2 | @using Microsoft.AspNetCore.Http 3 | @using Microsoft.AspNetCore.Http.Authentication 4 | @model cloudscribe.Web.SimpleAuth.ViewModels.LoginViewModel 5 | 6 | @if (!string.IsNullOrEmpty(Model.RecaptchaSiteKey)) 7 | { 8 | 9 | } 10 | 11 | @{ 12 | ViewData["Title"] = "Log in"; 13 | } 14 | 15 |

@ViewData["Title"].

16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 | 25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 |
43 | @if (Model.RecaptchaSiteKey.Length > 0) 44 | { 45 |
46 |
47 |
48 | @Html.ValidationMessage("recaptchaerror", new { @class = "text-danger" }) 49 |
50 |
51 | 52 | } 53 |
54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 | 64 | @section Scripts { 65 | @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 66 | } 67 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Security.Claims 2 | @using Microsoft.AspNetCore.Http.Extensions 3 | @using cloudscribe.Web.SimpleAuth.Extensions 4 | 5 | @if (User.Identity.IsAuthenticated) 6 | { 7 | 15 | } 16 | else 17 | { 18 | 21 | } 22 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /src/cloudscribe.Web.SimpleAuth/cloudscribe.Web.SimpleAuth.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | simple, no database required authentication for ASP.NET Core 5 | 1.1.1 6 | Joe Audette 7 | netstandard1.6 8 | cloudscribe.Web.SimpleAuth 9 | cloudscribe.Web.SimpleAuth 10 | cloudscribe;authentication;ASP.NET;MVC 11 | https://raw.githubusercontent.com/joeaudette/cloudscribe/master/cloudscribe-icon-32.png 12 | https://github.com/joeaudette/cloudscribe.Web.SimpleAuth 13 | http://www.apache.org/licenses/LICENSE-2.0 14 | false 15 | false 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/example.WebApp/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /src/example.WebApp/AppTenant.cs: -------------------------------------------------------------------------------- 1 |  2 | using cloudscribe.Web.SimpleAuth.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace example.WebApp 9 | { 10 | public class AppTenant 11 | { 12 | public string Id { get; set; } 13 | public string Name { get; set; } 14 | public string[] Hostnames { get; set; } 15 | public string Theme { get; set; } 16 | public string ConnectionString { get; set; } 17 | public List Users { get; set; } 18 | 19 | // overrides for default simpleauthsettings 20 | public bool EnablePasswordHasherUi { get; set; } = false; 21 | public string RecaptchaPublicKey { get; set; } 22 | public string RecaptchaPrivateKey { get; set; } 23 | public string AuthenticationScheme { get; set; } = "application"; 24 | 25 | public bool Equals(AppTenant other) 26 | { 27 | if (other == null) 28 | { 29 | return false; 30 | } 31 | 32 | return other.Name.Equals(Id); 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | return Equals(obj as AppTenant); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | return Id.GetHashCode(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/example.WebApp/AppTenantAuthSettingsResolver.cs: -------------------------------------------------------------------------------- 1 |  2 | using cloudscribe.Web.SimpleAuth.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace example.WebApp 9 | { 10 | public class AppTenantAuthSettingsResolver : IAuthSettingsResolver 11 | { 12 | public AppTenantAuthSettingsResolver(AppTenant tenant) 13 | { 14 | this.tenant = tenant; 15 | 16 | authSettings = new SimpleAuthSettings(); 17 | authSettings.AuthenticationScheme = tenant.AuthenticationScheme; 18 | authSettings.RecaptchaPrivateKey = tenant.RecaptchaPrivateKey; 19 | authSettings.RecaptchaPublicKey = tenant.RecaptchaPublicKey; 20 | authSettings.EnablePasswordHasherUi = tenant.EnablePasswordHasherUi; 21 | } 22 | 23 | private AppTenant tenant; 24 | private SimpleAuthSettings authSettings; 25 | 26 | public SimpleAuthSettings GetCurrentAuthSettings() 27 | { 28 | return authSettings; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/example.WebApp/AppTenantUserLookupProvider.cs: -------------------------------------------------------------------------------- 1 |  2 | using cloudscribe.Web.SimpleAuth.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace example.WebApp 9 | { 10 | public class AppTenantUserLookupProvider : IUserLookupProvider 11 | { 12 | public AppTenantUserLookupProvider(AppTenant tenant) 13 | { 14 | this.tenant = tenant; 15 | } 16 | 17 | private AppTenant tenant; 18 | 19 | public SimpleAuthUser GetUser(string userName) 20 | { 21 | foreach (SimpleAuthUser u in tenant.Users) 22 | { 23 | if (u.UserName == userName) { return u; } 24 | } 25 | 26 | return null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/example.WebApp/CachingAppTenantResolver.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using SaasKit.Multitenancy; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | 13 | namespace example.WebApp 14 | { 15 | public class CachingAppTenantResolver : MemoryCacheTenantResolver 16 | { 17 | private readonly IEnumerable tenants; 18 | 19 | public CachingAppTenantResolver( 20 | IMemoryCache cache, 21 | ILoggerFactory loggerFactory, 22 | IOptions options) 23 | : base(cache, loggerFactory) 24 | { 25 | this.tenants = options.Value.Tenants; 26 | } 27 | 28 | protected override string GetContextIdentifier(HttpContext context) 29 | { 30 | return context.Request.Host.Value.ToLower(); 31 | } 32 | 33 | protected override IEnumerable GetTenantIdentifiers(TenantContext context) 34 | { 35 | return context.Tenant.Hostnames; 36 | } 37 | 38 | protected override Task> ResolveAsync(HttpContext context) 39 | { 40 | TenantContext tenantContext = null; 41 | 42 | var tenant = tenants.FirstOrDefault(t => 43 | t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower()))); 44 | 45 | if (tenant != null) 46 | { 47 | tenantContext = new TenantContext(tenant); 48 | } 49 | 50 | return Task.FromResult(tenantContext); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/example.WebApp/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Authorization; 7 | 8 | namespace example.WebApp.Controllers 9 | { 10 | public class HomeController : Controller 11 | { 12 | public IActionResult Index() 13 | { 14 | return View(); 15 | } 16 | 17 | public IActionResult About() 18 | { 19 | ViewData["Message"] = "Your application description page."; 20 | 21 | return View(); 22 | } 23 | 24 | public IActionResult Contact() 25 | { 26 | ViewData["Message"] = "Your contact page."; 27 | 28 | return View(); 29 | } 30 | 31 | public IActionResult Error() 32 | { 33 | return View(); 34 | } 35 | 36 | [Authorize(Policy = "AdminPolicy")] 37 | public IActionResult Administration() 38 | { 39 | ViewData["Message"] = "Administrators only."; 40 | 41 | return View(); 42 | } 43 | 44 | [Authorize(Policy = "MembersOnlyPolicy")] 45 | public IActionResult Members() 46 | { 47 | ViewData["Message"] = "Members only."; 48 | 49 | return View(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/example.WebApp/MultiTenancyOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace example.WebApp 7 | { 8 | public class MultiTenancyOptions 9 | { 10 | public List Tenants { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/example.WebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace example.WebApp 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/example.WebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:54912/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "example.WebApp": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/example.WebApp/TenantViewLocationExpander.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc.Razor; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace example.WebApp 11 | { 12 | public class TenantViewLocationExpander : IViewLocationExpander 13 | { 14 | private const string THEME_KEY = "theme", TENANT_KEY = "tenant"; 15 | 16 | public void PopulateValues(ViewLocationExpanderContext context) 17 | { 18 | context.Values[THEME_KEY] 19 | = context.ActionContext.HttpContext.GetTenant()?.Theme; 20 | 21 | context.Values[TENANT_KEY] 22 | = context.ActionContext.HttpContext.GetTenant()?.Name.Replace(" ", "-"); 23 | } 24 | 25 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) 26 | { 27 | string theme = null; 28 | if (context.Values.TryGetValue(THEME_KEY, out theme)) 29 | { 30 | IEnumerable themeLocations = new[] 31 | { 32 | $"/Themes/{theme}/{{1}}/{{0}}.cshtml", 33 | $"/Themes/{theme}/Shared/{{0}}.cshtml" 34 | }; 35 | 36 | string tenant; 37 | if (context.Values.TryGetValue(TENANT_KEY, out tenant)) 38 | { 39 | themeLocations = ExpandTenantLocations(tenant, themeLocations); 40 | } 41 | 42 | viewLocations = themeLocations.Concat(viewLocations); 43 | } 44 | 45 | 46 | return viewLocations; 47 | } 48 | 49 | private IEnumerable ExpandTenantLocations(string tenant, IEnumerable defaultLocations) 50 | { 51 | foreach (var location in defaultLocations) 52 | { 53 | yield return location.Replace("{0}", $"{{0}}_{tenant}"); 54 | yield return location; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/example.WebApp/Themes/Cerulean/Home/Index_tenant-1.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 |

Tenant 1 Override

-------------------------------------------------------------------------------- /src/example.WebApp/Themes/Cerulean/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject AppTenant Tenant 2 | 3 | 4 | 5 | 6 | 7 | @ViewData["Title"] - @(Tenant?.Name ?? "SaasKit Sample") 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 41 |
42 | @await Component.InvokeAsync("Navigation", new { viewName = "BootstrapBreadcrumbs", filterName = NamedNavigationFilters.Breadcrumbs, startingNodeKey = "" }) 43 | @RenderBody() 44 |
45 |
46 |

© 2016 - SaasKit Sample Cerulean theme

47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 60 | 64 | 65 | 66 | 67 | @RenderSection("scripts", required: false) 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/example.WebApp/Themes/Darkly/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

This is the Darkly theme.

8 | -------------------------------------------------------------------------------- /src/example.WebApp/Themes/Darkly/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject AppTenant Tenant 2 | 3 | 4 | 5 | 6 | 7 | @ViewData["Title"] - @(Tenant?.Name ?? "SaasKit Sample") 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 41 |
42 | @await Component.InvokeAsync("Navigation", new { viewName = "BootstrapBreadcrumbs", filterName = NamedNavigationFilters.Breadcrumbs, startingNodeKey = "" }) 43 | @RenderBody() 44 |
45 |
46 |

© 2016 - SaasKit Sample

47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 60 | 64 | 65 | 66 | 67 | @RenderSection("scripts", required: false) 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/example.WebApp/Themes/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using example.WebApp 2 | @using cloudscribe.Web.Navigation 3 | @using Microsoft.AspNetCore.Identity 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | -------------------------------------------------------------------------------- /src/example.WebApp/Themes/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Home/Administration.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Administration"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 | This view requires the administrators role. 8 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 67 | 68 | 110 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Home/Members.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Members Only"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 | This view requires the Members role. 8 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/Components/Navigation/BootstrapBreadcrumbs.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Web.Navigation 2 | 3 | @model NavigationViewModel 4 | 5 | @{ 6 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 7 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 8 | // Author: Joe Audette 9 | // Created 2015-07-11 10 | // Last Modified: 2015-07-15 11 | } 12 | 13 | @if (Model.CurrentNode != null && Model.ParentChain.Count > 1) 14 | { 15 | @: 32 | } 33 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/Components/Navigation/BootstrapTopNav.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Web.Navigation 2 | 3 | @model NavigationViewModel 4 | 5 | @{ 6 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 7 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 8 | // Author: Joe Audette 9 | // Created 2015-07-11 10 | // Last Modified: 2015-07-15 11 | 12 | } 13 | 14 | @if (Model.HasVisibleChildren(Model.RootNode)) 15 | { 16 | @: 26 | } 27 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/Components/Navigation/BootstrapTopNavWithDropdowns.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Web.Navigation 2 | @using System.Text 3 | 4 | @model NavigationViewModel 5 | 6 | @{ 7 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 8 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 9 | // Author: Joe Audette 10 | // Created 2015-07-11 11 | // Last Modified: 2015-07-15 12 | 13 | 14 | // probably don't want to implement dropdowns as they are not mobile friendly and not officially supported by bootstrap 15 | // http://stackoverflow.com/questions/18023493/bootstrap-3-dropdown-sub-menu-missing 16 | } 17 | 18 | @if (Model.HasVisibleChildren(Model.RootNode)) 19 | { 20 | @: 42 | } 43 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/Components/Navigation/ChildTree.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Web.Navigation 2 | @using System.Text 3 | 4 | @model NavigationViewModel 5 | 6 | @{ 7 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 8 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 9 | // Author: Joe Audette 10 | // Created 2015-07-11 11 | // Last Modified: 2015-07-15 12 | } 13 | 14 | @if (Model.HasVisibleChildren(Model.CurrentNode)) 15 | { 16 | @:
    17 | foreach (var node in Model.CurrentNode.Children) 18 | { 19 | if (!Model.ShouldAllowView(node)) { continue; } 20 | if (!Model.HasVisibleChildren(node)) 21 | { 22 | @: 23 |
  • @Model.AdjustText(node)
  • 24 | } 25 | else 26 | { 27 | @: 28 |
  • @Model.AdjustText(node) 29 | @Model.UpdateTempNode(node) 30 | @Html.Partial("NavigationNodeChildTreePartial", Model) 31 |
  • 32 | } 33 | } 34 | @:
35 | } 36 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/Components/Navigation/Default.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Web.Navigation 2 | 3 | @model NavigationViewModel 4 | 5 | 6 | @{ 7 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 8 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 9 | // Author: Joe Audette 10 | // Created 2015-07-11 11 | // Last Modified: 2015-07-15 12 | 13 | } 14 | 15 | @if (Model.HasVisibleChildren(Model.RootNode)) 16 | { 17 | @: 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/Components/Navigation/SideNav.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Web.Navigation 2 | @using System.Text 3 | 4 | @model NavigationViewModel 5 | 6 | @{ 7 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 8 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 9 | // Author: Joe Audette 10 | // Created 2015-09-10 11 | // Last Modified: 2015-09-16 12 | } 13 | 14 | @if (Model.HasVisibleChildren(Model.StartingNode)) 15 | { 16 | @: 64 | } 65 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

10 | Swapping to Development environment will display more detailed information about the error that occurred. 11 |

12 |

13 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 14 |

15 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/NavigationNodeChildDropdownPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Core.Web.Navigation 2 | 3 | @model NavigationViewModel 4 | 5 | @{ 6 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 7 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 8 | // Author: Joe Audette 9 | // Created 2015-07-13 10 | // Last Modified: 2015-07-13 11 | } 12 | 13 | 14 | @if ((Model.TempNode != null) && (Model.HasVisibleChildren(Model.TempNode))) 15 | { 16 | @: 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/NavigationNodeChildTreePartial.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Core.Web.Navigation 2 | 3 | @model NavigationViewModel 4 | 5 | @{ 6 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 7 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 8 | // Author: Joe Audette 9 | // Created 2015-07-13 10 | // Last Modified: 2015-07-13 11 | } 12 | 13 | @if ((Model.TempNode != null) && (Model.HasVisibleChildren(Model.TempNode))) 14 | { 15 | @:
    16 | 17 | @foreach (var childNode in Model.TempNode.Children) 18 | { 19 | if (!Model.ShouldAllowView(childNode)) { continue; } 20 | 21 | if (!Model.HasVisibleChildren(childNode)) 22 | { 23 | @: 24 |
  • @childNode.Value.Text
  • 25 | } 26 | else 27 | { 28 |
  • 29 | 30 | @childNode.Value.Text 31 | 32 | @Model.UpdateTempNode(childNode) 33 | @Html.Partial("NavigationNodeChildTreePartial", Model) @* recursion *@ 34 |
  • 35 | } 36 | } 37 | 38 | @:
39 | 40 | } -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/NavigationNodeSideNavPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using cloudscribe.Core.Web.Navigation 2 | 3 | @model NavigationViewModel 4 | 5 | @{ 6 | // Copyright (c) Source Tree Solutions, LLC. All rights reserved. 7 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 8 | // Author: Joe Audette 9 | // Created 2015-09-10 10 | // Last Modified: 2015-09-16 11 | } 12 | 13 | @if ((Model.TempNode != null) && (Model.HasVisibleChildren(Model.TempNode))) 14 | { 15 | @: 63 | 64 | } -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject AppTenant Tenant 2 | 3 | 4 | 5 | 6 | 7 | @ViewData["Title"] - example.WebApp 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 38 |
39 | @await Component.InvokeAsync("Navigation", new { viewName = "BootstrapBreadcrumbs", filterName = NamedNavigationFilters.Breadcrumbs, startingNodeKey = "" }) 40 | @RenderBody() 41 |
42 |
43 |

© 2016 - example.WebApp

44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 61 | 62 | 63 | 64 | @RenderSection("scripts", required: false) 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using example.WebApp 2 | @using cloudscribe.Web.Navigation 3 | @using Microsoft.AspNetCore.Identity 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | -------------------------------------------------------------------------------- /src/example.WebApp/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/example.WebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "Logging": { 4 | "IncludeScopes": false, 5 | "LogLevel": { 6 | "Default": "Debug", 7 | "System": "Information", 8 | "Microsoft": "Information" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/example.WebApp/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.6", 6 | "jquery": "2.2.0", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/example.WebApp/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optinally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /src/example.WebApp/example.WebApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1 5 | true 6 | example.WebApp 7 | Exe 8 | example.WebApp 9 | aspnet-example.WebApp-c572acd7-734e-4ecf-9ecf-856932c7c49a 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/example.WebApp/navigation.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/src/example.WebApp/navigation.xml -------------------------------------------------------------------------------- /src/example.WebApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "gulp": "3.8.11", 7 | "gulp-concat": "2.5.2", 8 | "gulp-cssmin": "0.1.7", 9 | "gulp-uglify": "1.2.0", 10 | "rimraf": "2.2.8" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/example.WebApp/simpleauthsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SimpleAuthSettings": { 3 | "EnablePasswordHasherUi": "true", 4 | "Comment-Recaptcha": "get your recaptcha keys here https://www.google.com/recaptcha/intro/index.html", 5 | "RecaptchaPublicKey": "", 6 | "RecaptchaPrivateKey": "", 7 | "AuthenticationScheme": "application" 8 | 9 | }, 10 | 11 | "MultiTenancy": { 12 | "Tenants": [ 13 | { 14 | "Id": "tenant1", 15 | "Name": "sample site", 16 | "Hostnames": [ 17 | "localhost:60000", 18 | "tenant1.local:60000", 19 | "localhost:54912" 20 | ], 21 | "Theme": "Cerulean", 22 | "EnablePasswordHasherUi": "true", 23 | "Comment-Recaptcha": "get your recaptcha keys here https://www.google.com/recaptcha/intro/index.html", 24 | "RecaptchaPublicKey": "", 25 | "RecaptchaPrivateKey": "", 26 | "AuthenticationScheme": "application", 27 | "Users": [ 28 | { 29 | "UserName": "admin", 30 | "FYI-Comment": "this hash is a hash of the word admin, to create your own password hash, set EnablePasswordHasherUi as true, and visit /Login/HashPassword", 31 | "Password": "AQAAAAEAACcQAAAAEKyu1zRJBNHJUw4r1mJwxgHfvofBmIAZSiG8X1O8dmdBFiQT0183/oqeoo0qGHv/Hw==", 32 | "PasswordIsHashed": "true", 33 | "Claims": [ 34 | { 35 | "ClaimType": "DisplayName", 36 | "ClaimValue": "Rock Star" 37 | }, 38 | { 39 | "ClaimType": "Email", 40 | "ClaimValue": "demo@demo.com" 41 | }, 42 | { 43 | "ClaimType": "Role", 44 | "ClaimValue": "Admins" 45 | } 46 | 47 | ] 48 | }, 49 | { 50 | "UserName": "member", 51 | "Password": "member", 52 | "PasswordIsHashed": "false", 53 | "Claims": [ 54 | { 55 | "ClaimType": "DisplayName", 56 | "ClaimValue": "Mr Awesome" 57 | }, 58 | { 59 | "ClaimType": "Email", 60 | "ClaimValue": "demo2@demo.com" 61 | }, 62 | { 63 | "ClaimType": "Role", 64 | "ClaimValue": "Members" 65 | } 66 | 67 | ] 68 | } 69 | ] 70 | }, 71 | { 72 | "Id": "tenant2", 73 | "Name": "darkly industries", 74 | "Hostnames": [ 75 | "localhost:60002", 76 | "tenant2.local:60002" 77 | ], 78 | "Theme": "Darkly", 79 | "EnablePasswordHasherUi": "true", 80 | "RecaptchaPublicKey": "", 81 | "RecaptchaPrivateKey": "", 82 | "AuthenticationScheme": "application", 83 | "Users": [ 84 | { 85 | "UserName": "admin", 86 | "FYI-Comment": "this hash is a hash of the word admin, to create your own password hash, set EnablePasswordHasherUi as true, and visit /Login/HashPassword", 87 | "Password": "AQAAAAEAACcQAAAAEKyu1zRJBNHJUw4r1mJwxgHfvofBmIAZSiG8X1O8dmdBFiQT0183/oqeoo0qGHv/Hw==", 88 | "PasswordIsHashed": "true", 89 | "Claims": [ 90 | { 91 | "ClaimType": "DisplayName", 92 | "ClaimValue": "Rock Star Darkly" 93 | }, 94 | { 95 | "ClaimType": "Email", 96 | "ClaimValue": "demo@demo.com" 97 | }, 98 | { 99 | "ClaimType": "Role", 100 | "ClaimValue": "Admins" 101 | } 102 | 103 | ] 104 | }, 105 | { 106 | "UserName": "member", 107 | "Password": "member", 108 | "PasswordIsHashed": "false", 109 | "Claims": [ 110 | { 111 | "ClaimType": "DisplayName", 112 | "ClaimValue": "Mr Awesome Darkly" 113 | }, 114 | { 115 | "ClaimType": "Email", 116 | "ClaimValue": "demo2@demo.com" 117 | }, 118 | { 119 | "ClaimType": "Role", 120 | "ClaimValue": "Members" 121 | } 122 | 123 | ] 124 | } 125 | ] 126 | } 127 | ] 128 | } 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/example.WebApp/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/example.WebApp/wwwroot/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | -------------------------------------------------------------------------------- /src/example.WebApp/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption p { 22 | font-size: 20px; 23 | line-height: 1.4; 24 | } 25 | 26 | /* buttons and links extension to use brackets: [ click me ] */ 27 | .btn-bracketed::before { 28 | display:inline-block; 29 | content: "["; 30 | padding-right: 0.5em; 31 | } 32 | .btn-bracketed::after { 33 | display:inline-block; 34 | content: "]"; 35 | padding-left: 0.5em; 36 | } 37 | 38 | /* Hide/rearrange for smaller screens */ 39 | @media screen and (max-width: 767px) { 40 | /* Hide captions */ 41 | .carousel-caption { 42 | display: none 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/example.WebApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudscribe/cloudscribe.Web.SimpleAuth/a25639f73c478b7c08bd8a6b695d18f2f478489f/src/example.WebApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/example.WebApp/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | --------------------------------------------------------------------------------