├── .gitignore ├── ADB2C └── Web │ ├── Controllers │ ├── AuthController.cs │ └── HomeController.cs │ ├── Program.cs │ ├── Startup.cs │ ├── Views │ ├── Auth │ │ └── AccessDenied.cshtml │ ├── Home │ │ ├── Index.cshtml │ │ └── UserInformation.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml │ ├── Web.csproj │ └── wwwroot │ └── styles │ ├── css │ ├── Site.css │ ├── bootstrap.css │ ├── bootstrap.min.css │ └── bootstrap.min.css.map │ └── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── Demo.sln ├── IdentityServer ├── IdentityServer │ ├── IdentityServer.csproj │ ├── Program.cs │ ├── Quickstart │ │ ├── Account │ │ │ ├── AccountController.cs │ │ │ ├── AccountOptions.cs │ │ │ ├── AccountService.cs │ │ │ ├── ExternalProvider.cs │ │ │ ├── LoggedOutViewModel.cs │ │ │ ├── LoginInputModel.cs │ │ │ ├── LoginViewModel.cs │ │ │ ├── LogoutInputModel.cs │ │ │ └── LogoutViewModel.cs │ │ ├── Consent │ │ │ ├── ConsentController.cs │ │ │ ├── ConsentInputModel.cs │ │ │ ├── ConsentOptions.cs │ │ │ ├── ConsentService.cs │ │ │ ├── ConsentViewModel.cs │ │ │ ├── ProcessConsentResult.cs │ │ │ └── ScopeViewModel.cs │ │ ├── Grants │ │ │ ├── GrantsController.cs │ │ │ └── GrantsViewModel.cs │ │ ├── Home │ │ │ ├── ErrorViewModel.cs │ │ │ └── HomeController.cs │ │ └── SecurityHeadersAttribute.cs │ ├── Startup.cs │ ├── Views │ │ ├── Account │ │ │ ├── LoggedOut.cshtml │ │ │ ├── Login.cshtml │ │ │ └── Logout.cshtml │ │ ├── Consent │ │ │ ├── Index.cshtml │ │ │ └── _ScopeListItem.cshtml │ │ ├── Grants │ │ │ └── Index.cshtml │ │ ├── Home │ │ │ └── Index.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ ├── _Layout.cshtml │ │ │ └── _ValidationSummary.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── appsettings.json │ ├── tempkey.rsa │ └── wwwroot │ │ ├── css │ │ ├── site.css │ │ ├── site.less │ │ └── site.min.css │ │ ├── favicon.ico │ │ ├── icon.jpg │ │ ├── icon.png │ │ ├── js │ │ └── signout-redirect.js │ │ └── lib │ │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ └── bootstrap.min.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ │ ├── bootstrap.js │ │ │ └── bootstrap.min.js │ │ └── jquery │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map └── Web │ ├── Controllers │ ├── Api │ │ └── ApiController.cs │ ├── AuthController.cs │ └── HomeController.cs │ ├── Program.cs │ ├── Startup.cs │ ├── Views │ ├── Auth │ │ └── AccessDenied.cshtml │ ├── Home │ │ ├── Index.cshtml │ │ ├── Spa.cshtml │ │ └── UserInformation.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml │ ├── Web.csproj │ └── wwwroot │ ├── callback.html │ ├── scripts │ ├── oidc-client-config.js │ ├── oidc-client.js │ └── oidc-client.min.js │ ├── signoutcallback.html │ └── styles │ ├── css │ ├── Site.css │ ├── bootstrap.css │ ├── bootstrap.min.css │ └── bootstrap.min.css.map │ └── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── Local └── Web │ ├── Controllers │ ├── AuthController.cs │ └── HomeController.cs │ ├── Models │ ├── LogInModel.cs │ ├── RegisterExternalModel.cs │ └── RegisterModel.cs │ ├── Program.cs │ ├── Services │ ├── DummyUserService.cs │ ├── IUserService.cs │ └── User.cs │ ├── Startup.cs │ ├── Views │ ├── Auth │ │ ├── AccessDenied.cshtml │ │ ├── LogIn.cshtml │ │ ├── Register.cshtml │ │ └── RegisterExternal.cshtml │ ├── Home │ │ ├── Index.cshtml │ │ └── UserInformation.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml │ ├── Web.csproj │ └── wwwroot │ └── styles │ ├── css │ ├── Site.css │ ├── bootstrap.css │ ├── bootstrap.min.css │ └── bootstrap.min.css.map │ └── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/visualstudio 3 | 4 | ### VisualStudio ### 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # .NET Core 50 | project.lock.json 51 | project.fragment.lock.json 52 | artifacts/ 53 | **/Properties/launchSettings.json 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # TFS 2012 Local Workspace 101 | $tf/ 102 | 103 | # Guidance Automation Toolkit 104 | *.gpState 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper*/ 108 | *.[Rr]e[Ss]harper 109 | *.DotSettings.user 110 | 111 | # JustCode is a .NET coding add-in 112 | .JustCode 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # Visual Studio code coverage results 121 | *.coverage 122 | *.coveragexml 123 | 124 | # NCrunch 125 | _NCrunch_* 126 | .*crunch*.local.xml 127 | nCrunchTemp_* 128 | 129 | # MightyMoose 130 | *.mm.* 131 | AutoTest.Net/ 132 | 133 | # Web workbench (sass) 134 | .sass-cache/ 135 | 136 | # Installshield output folder 137 | [Ee]xpress/ 138 | 139 | # DocProject is a documentation generator add-in 140 | DocProject/buildhelp/ 141 | DocProject/Help/*.HxT 142 | DocProject/Help/*.HxC 143 | DocProject/Help/*.hhc 144 | DocProject/Help/*.hhk 145 | DocProject/Help/*.hhp 146 | DocProject/Help/Html2 147 | DocProject/Help/html 148 | 149 | # Click-Once directory 150 | publish/ 151 | 152 | # Publish Web Output 153 | *.[Pp]ublish.xml 154 | *.azurePubxml 155 | # TODO: Uncomment the next line to ignore your web deploy settings. 156 | # By default, sensitive information, such as encrypted password 157 | # should be stored in the .pubxml.user file. 158 | #*.pubxml 159 | *.pubxml.user 160 | *.publishproj 161 | 162 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 163 | # checkin your Azure Web App publish settings, but sensitive information contained 164 | # in these scripts will be unencrypted 165 | PublishScripts/ 166 | 167 | # NuGet Packages 168 | *.nupkg 169 | # The packages folder can be ignored because of Package Restore 170 | **/packages/* 171 | # except build/, which is used as an MSBuild target. 172 | !**/packages/build/ 173 | # Uncomment if necessary however generally it will be regenerated when needed 174 | #!**/packages/repositories.config 175 | # NuGet v3's project.json files produces more ignorable files 176 | *.nuget.props 177 | *.nuget.targets 178 | 179 | # Microsoft Azure Build Output 180 | csx/ 181 | *.build.csdef 182 | 183 | # Microsoft Azure Emulator 184 | ecf/ 185 | rcf/ 186 | 187 | # Windows Store app package directories and files 188 | AppPackages/ 189 | BundleArtifacts/ 190 | Package.StoreAssociation.xml 191 | _pkginfo.txt 192 | 193 | # Visual Studio cache files 194 | # files ending in .cache can be ignored 195 | *.[Cc]ache 196 | # but keep track of directories ending in .cache 197 | !*.[Cc]ache/ 198 | 199 | # Others 200 | ClientBin/ 201 | ~$* 202 | *~ 203 | *.dbmdl 204 | *.dbproj.schemaview 205 | *.jfm 206 | *.pfx 207 | *.publishsettings 208 | orleans.codegen.cs 209 | 210 | # Since there are multiple workflows, uncomment next line to ignore bower_components 211 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 212 | #bower_components/ 213 | 214 | # RIA/Silverlight projects 215 | Generated_Code/ 216 | 217 | # Backup & report files from converting an old project file 218 | # to a newer Visual Studio version. Backup files are not needed, 219 | # because we have git ;-) 220 | _UpgradeReport_Files/ 221 | Backup*/ 222 | UpgradeLog*.XML 223 | UpgradeLog*.htm 224 | 225 | # SQL Server files 226 | *.mdf 227 | *.ldf 228 | *.ndf 229 | 230 | # Business Intelligence projects 231 | *.rdl.data 232 | *.bim.layout 233 | *.bim_*.settings 234 | 235 | # Microsoft Fakes 236 | FakesAssemblies/ 237 | 238 | # GhostDoc plugin setting file 239 | *.GhostDoc.xml 240 | 241 | # Node.js Tools for Visual Studio 242 | .ntvs_analysis.dat 243 | node_modules/ 244 | 245 | # Typescript v1 declaration files 246 | typings/ 247 | 248 | # Visual Studio 6 build log 249 | *.plg 250 | 251 | # Visual Studio 6 workspace options file 252 | *.opt 253 | 254 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 255 | *.vbw 256 | 257 | # Visual Studio LightSwitch build output 258 | **/*.HTMLClient/GeneratedArtifacts 259 | **/*.DesktopClient/GeneratedArtifacts 260 | **/*.DesktopClient/ModelManifest.xml 261 | **/*.Server/GeneratedArtifacts 262 | **/*.Server/ModelManifest.xml 263 | _Pvt_Extensions 264 | 265 | # Paket dependency manager 266 | .paket/paket.exe 267 | paket-files/ 268 | 269 | # FAKE - F# Make 270 | .fake/ 271 | 272 | # JetBrains Rider 273 | .idea/ 274 | *.sln.iml 275 | 276 | # CodeRush 277 | .cr/ 278 | 279 | # Python Tools for Visual Studio (PTVS) 280 | __pycache__/ 281 | *.pyc 282 | 283 | # Cake - Uncomment if you are using it 284 | # tools/** 285 | # !tools/packages.config 286 | 287 | # Telerik's JustMock configuration file 288 | *.jmconfig 289 | 290 | # BizTalk build output 291 | *.btp.cs 292 | *.btm.cs 293 | *.odx.cs 294 | *.xsd.cs 295 | 296 | ### VisualStudio Patch ### 297 | # By default, sensitive information, such as encrypted password 298 | # should be stored in the .pubxml.user file. 299 | 300 | # End of https://www.gitignore.io/api/visualstudio -------------------------------------------------------------------------------- /ADB2C/Web/Controllers/AuthController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Authentication; 4 | using System.Security.Claims; 5 | 6 | namespace Web.Controllers 7 | { 8 | [Route("auth")] 9 | public class AuthController : Controller 10 | { 11 | [Route("login")] 12 | public IActionResult LogIn() 13 | { 14 | return Challenge(new AuthenticationProperties { RedirectUri = "/userinfo" }, "B2C_1_sign_in"); 15 | } 16 | 17 | [Route("logout")] 18 | [ValidateAntiForgeryToken] 19 | [HttpPost] 20 | public async Task LogOut() 21 | { 22 | await HttpContext.SignOutAsync(); 23 | var authscheme = User.FindFirstValue("tfp"); 24 | await HttpContext.SignOutAsync(authscheme); 25 | } 26 | 27 | [Route("register")] 28 | public IActionResult Register() 29 | { 30 | return Challenge(new AuthenticationProperties { RedirectUri = "/userinfo" }, "B2C_1_sign_up"); 31 | } 32 | 33 | [Route("accessdenied")] 34 | public IActionResult AccessDenied() 35 | { 36 | return View(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /ADB2C/Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace Web.Controllers 5 | { 6 | public class HomeController : Controller 7 | { 8 | [Route("")] 9 | public IActionResult Index() 10 | { 11 | return View(); 12 | } 13 | 14 | [Route("userinfo")] 15 | [Authorize] 16 | public IActionResult UserInformation() 17 | { 18 | return View(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /ADB2C/Web/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace Web 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ADB2C/Web/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.Cookies; 2 | using Microsoft.AspNetCore.Authentication.OpenIdConnect; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 7 | 8 | namespace Web 9 | { 10 | public class Startup 11 | { 12 | public void ConfigureServices(IServiceCollection services) 13 | { 14 | services.AddMvc(); 15 | 16 | services.AddAuthentication(options => { 17 | options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; 18 | options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme; 19 | options.DefaultChallengeScheme = "B2C_1_sign_in_or_up"; 20 | options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; 21 | }) 22 | .AddCookie() 23 | .AddOpenIdConnect("B2C_1_sign_in_or_up", options => ConfigureOpenIdOptions(options, "B2C_1_sign_in_or_up")) 24 | .AddOpenIdConnect("B2C_1_sign_in", options => ConfigureOpenIdOptions(options, "B2C_1_sign_in")) 25 | .AddOpenIdConnect("B2C_1_sign_up", options => ConfigureOpenIdOptions(options, "B2C_1_sign_up")); 26 | } 27 | 28 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 29 | { 30 | if (env.IsDevelopment()) 31 | { 32 | app.UseDeveloperExceptionPage(); 33 | } 34 | 35 | app.UseStaticFiles(); 36 | 37 | app.UseAuthentication(); 38 | 39 | app.UseMvc(); 40 | } 41 | 42 | private void ConfigureOpenIdOptions(OpenIdConnectOptions options, string policy) 43 | { 44 | options.MetadataAddress = "https://login.microsoftonline.com/zerokollauthdemo.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=" + policy; 45 | options.ClientId = "### INSERT YOUR CLIENTID HERE ###"; 46 | options.ResponseType = OpenIdConnectResponseType.IdToken; 47 | options.SignedOutRedirectUri = "/"; 48 | options.CallbackPath = "/signin-oidc-" + policy; 49 | options.SignedOutCallbackPath = "/signout-oidc-" + policy; 50 | options.TokenValidationParameters.NameClaimType = "name"; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ADB2C/Web/Views/Auth/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Access Denied"; 3 | } 4 | 5 |
6 |

Access Denied

7 |

8 |

You are not welcome!

9 |

10 |
11 | 12 | -------------------------------------------------------------------------------- /ADB2C/Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 
2 |

Hello @(User.Identity.IsAuthenticated ? User.Identity.Name : "World")

3 | @if (User.Identity.IsAuthenticated) 4 | { 5 |
6 | Log Out 7 |
8 | } 9 | else 10 | { 11 |

12 | Register 13 | Log In 14 |

15 | } 16 |
-------------------------------------------------------------------------------- /ADB2C/Web/Views/Home/UserInformation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "User Information"; 3 | } 4 | 5 |
6 |

User Information

7 |

8 |

9 | @User.Identity.Name 10 |
11 |
12 |
13 | @foreach (var claim in User.Claims) 14 | { 15 |
@claim.Issuer | @claim.Type | @claim.Value
16 | } 17 |
18 |

19 | 20 |
21 | Log Out 22 |
23 |
24 | 25 | -------------------------------------------------------------------------------- /ADB2C/Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | 8 | 9 | 10 | 11 |
12 | 13 | 38 | 39 | @RenderBody() 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /ADB2C/Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /ADB2C/Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /ADB2C/Web/Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ADB2C/Web/wwwroot/styles/css/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 20px; 3 | } 4 | 5 | .form, 6 | .validation-summary-errors { 7 | max-width: 330px; 8 | padding: 15px; 9 | margin: 0 auto; 10 | } 11 | 12 | .form .form-heading { 13 | margin-bottom: 10px; 14 | } 15 | 16 | .form .form-control { 17 | position: relative; 18 | height: auto; 19 | -webkit-box-sizing: border-box; 20 | -moz-box-sizing: border-box; 21 | box-sizing: border-box; 22 | padding: 10px; 23 | font-size: 16px; 24 | } 25 | 26 | .form .form-control:focus { 27 | z-index: 2; 28 | } 29 | 30 | .form input { 31 | border-radius: 0; 32 | margin-bottom: -1px; 33 | } 34 | 35 | .form .first { 36 | margin-bottom: -1px; 37 | border-top-right-radius: 5px; 38 | border-top-left-radius: 5px; 39 | } 40 | 41 | .form .last { 42 | margin-bottom: 10px; 43 | border-bottom-left-radius: 5px; 44 | border-bottom-right-radius: 5px; 45 | } 46 | 47 | .navbar-nav > li a { 48 | position: relative; 49 | display: block; 50 | padding: 15px; 51 | color: #777; 52 | } 53 | 54 | .navbar-default .navbar-nav > li a:hover, 55 | .navbar-nav > li > a:hover { 56 | text-decoration: none; 57 | color: #fff; 58 | background-color: #999; 59 | } 60 | 61 | .external-providers { 62 | max-width: 330px; 63 | padding: 15px; 64 | margin: auto; 65 | } 66 | 67 | .external-providers a { 68 | width: 100%; 69 | margin-bottom: 10px; 70 | } -------------------------------------------------------------------------------- /ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/ADB2C/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Demo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Local", "1. Local", "{4A4ADE5E-4036-4AFE-935D-CC4D4A3EC9AA}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. Azure AD B2C", "2. Azure AD B2C", "{225178B9-FC5C-46AB-92A4-1F7327E59818}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3. IdentityServer", "3. IdentityServer", "{0C6BEA42-5AA9-4579-9034-A43B22A4CD82}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "Local\Web\Web.csproj", "{5487B848-F00B-45DD-AEA0-5AF0975EF561}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "ADB2C\Web\Web.csproj", "{347EFA6F-0C2D-4658-A237-ED73539EED92}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "IdentityServer\Web\Web.csproj", "{1E4A9748-F547-4B29-BE61-B4B806366FA6}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "IdentityServer\IdentityServer\IdentityServer.csproj", "{F9D7D102-366F-4ADB-9B5F-11482E8C2448}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {5487B848-F00B-45DD-AEA0-5AF0975EF561}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5487B848-F00B-45DD-AEA0-5AF0975EF561}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5487B848-F00B-45DD-AEA0-5AF0975EF561}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {5487B848-F00B-45DD-AEA0-5AF0975EF561}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {347EFA6F-0C2D-4658-A237-ED73539EED92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {347EFA6F-0C2D-4658-A237-ED73539EED92}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {347EFA6F-0C2D-4658-A237-ED73539EED92}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {347EFA6F-0C2D-4658-A237-ED73539EED92}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {1E4A9748-F547-4B29-BE61-B4B806366FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {1E4A9748-F547-4B29-BE61-B4B806366FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {1E4A9748-F547-4B29-BE61-B4B806366FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {1E4A9748-F547-4B29-BE61-B4B806366FA6}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {F9D7D102-366F-4ADB-9B5F-11482E8C2448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {F9D7D102-366F-4ADB-9B5F-11482E8C2448}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {F9D7D102-366F-4ADB-9B5F-11482E8C2448}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {F9D7D102-366F-4ADB-9B5F-11482E8C2448}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(NestedProjects) = preSolution 47 | {5487B848-F00B-45DD-AEA0-5AF0975EF561} = {4A4ADE5E-4036-4AFE-935D-CC4D4A3EC9AA} 48 | {347EFA6F-0C2D-4658-A237-ED73539EED92} = {225178B9-FC5C-46AB-92A4-1F7327E59818} 49 | {1E4A9748-F547-4B29-BE61-B4B806366FA6} = {0C6BEA42-5AA9-4579-9034-A43B22A4CD82} 50 | {F9D7D102-366F-4ADB-9B5F-11482E8C2448} = {0C6BEA42-5AA9-4579-9034-A43B22A4CD82} 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {954B1462-F8DB-4623-BC8B-F670E8F778D1} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/IdentityServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using Serilog; 10 | using Serilog.Events; 11 | using Serilog.Sinks.SystemConsole.Themes; 12 | 13 | namespace IdentityServer 14 | { 15 | public class Program 16 | { 17 | public static void Main(string[] args) 18 | { 19 | Console.Title = "IdentityServer4"; 20 | 21 | BuildWebHost(args).Run(); 22 | } 23 | 24 | public static IWebHost BuildWebHost(string[] args) 25 | { 26 | Log.Logger = new LoggerConfiguration() 27 | .MinimumLevel.Debug() 28 | .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) 29 | .MinimumLevel.Override("System", LogEventLevel.Warning) 30 | .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) 31 | .Enrich.FromLogContext() 32 | .WriteTo.File(@"identityserver4_log.txt") 33 | .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate) 34 | .CreateLogger(); 35 | 36 | return WebHost.CreateDefaultBuilder(args) 37 | .UseStartup() 38 | //.ConfigureLogging(builder => 39 | //{ 40 | // builder.ClearProviders(); 41 | // builder.AddSerilog(); 42 | //}) 43 | .Build(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/AccountController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4.Services; 7 | using IdentityServer4.Stores; 8 | using IdentityServer4.Test; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.Mvc; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Security.Claims; 15 | using System.Security.Principal; 16 | using System.Threading.Tasks; 17 | using Microsoft.AspNetCore.Authentication; 18 | using IdentityServer4.Events; 19 | using IdentityServer4.Extensions; 20 | using IdentityServer4.Models; 21 | 22 | namespace IdentityServer4.Quickstart.UI 23 | { 24 | /// 25 | /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. 26 | /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! 27 | /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval 28 | /// 29 | [SecurityHeaders] 30 | public class AccountController : Controller 31 | { 32 | private readonly TestUserStore _users; 33 | private readonly IIdentityServerInteractionService _interaction; 34 | private readonly IEventService _events; 35 | private readonly AccountService _account; 36 | 37 | public AccountController( 38 | IIdentityServerInteractionService interaction, 39 | IClientStore clientStore, 40 | IHttpContextAccessor httpContextAccessor, 41 | IAuthenticationSchemeProvider schemeProvider, 42 | IEventService events, 43 | TestUserStore users = null) 44 | { 45 | // if the TestUserStore is not in DI, then we'll just use the global users collection 46 | _users = users; // ?? new TestUserStore(TestUsers.Users); 47 | _interaction = interaction; 48 | _events = events; 49 | _account = new AccountService(interaction, httpContextAccessor, schemeProvider, clientStore); 50 | } 51 | 52 | /// 53 | /// Show login page 54 | /// 55 | [HttpGet] 56 | public async Task Login(string returnUrl) 57 | { 58 | // build a model so we know what to show on the login page 59 | var vm = await _account.BuildLoginViewModelAsync(returnUrl); 60 | 61 | if (vm.IsExternalLoginOnly) 62 | { 63 | // we only have one option for logging in and it's an external provider 64 | return await ExternalLogin(vm.ExternalLoginScheme, returnUrl); 65 | } 66 | 67 | return View(vm); 68 | } 69 | 70 | /// 71 | /// Handle postback from username/password login 72 | /// 73 | [HttpPost] 74 | [ValidateAntiForgeryToken] 75 | public async Task Login(LoginInputModel model, string button) 76 | { 77 | if (button != "login") 78 | { 79 | // the user clicked the "cancel" button 80 | var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 81 | if (context != null) 82 | { 83 | // if the user cancels, send a result back into IdentityServer as if they 84 | // denied the consent (even if this client does not require consent). 85 | // this will send back an access denied OIDC error response to the client. 86 | await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); 87 | 88 | // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null 89 | return Redirect(model.ReturnUrl); 90 | } 91 | else 92 | { 93 | // since we don't have a valid context, then we just go back to the home page 94 | return Redirect("~/"); 95 | } 96 | } 97 | 98 | if (ModelState.IsValid) 99 | { 100 | // validate username/password against in-memory store 101 | if (_users.ValidateCredentials(model.Username, model.Password)) 102 | { 103 | var user = _users.FindByUsername(model.Username); 104 | await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); 105 | 106 | // only set explicit expiration here if user chooses "remember me". 107 | // otherwise we rely upon expiration configured in cookie middleware. 108 | AuthenticationProperties props = null; 109 | if (AccountOptions.AllowRememberLogin && model.RememberLogin) 110 | { 111 | props = new AuthenticationProperties 112 | { 113 | IsPersistent = true, 114 | ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) 115 | }; 116 | }; 117 | 118 | // issue authentication cookie with subject ID and username 119 | await HttpContext.SignInAsync(user.SubjectId, user.Username, props); 120 | 121 | // make sure the returnUrl is still valid, and if so redirect back to authorize endpoint or a local page 122 | if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl)) 123 | { 124 | return Redirect(model.ReturnUrl); 125 | } 126 | 127 | return Redirect("~/"); 128 | } 129 | 130 | await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); 131 | 132 | ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); 133 | } 134 | 135 | // something went wrong, show form with error 136 | var vm = await _account.BuildLoginViewModelAsync(model); 137 | return View(vm); 138 | } 139 | 140 | /// 141 | /// initiate roundtrip to external authentication provider 142 | /// 143 | [HttpGet] 144 | public async Task ExternalLogin(string provider, string returnUrl) 145 | { 146 | var props = new AuthenticationProperties() 147 | { 148 | RedirectUri = Url.Action("ExternalLoginCallback"), 149 | Items = 150 | { 151 | { "returnUrl", returnUrl } 152 | } 153 | }; 154 | 155 | // windows authentication needs special handling 156 | // since they don't support the redirect uri, 157 | // so this URL is re-triggered when we call challenge 158 | if (AccountOptions.WindowsAuthenticationSchemeName == provider) 159 | { 160 | // see if windows auth has already been requested and succeeded 161 | var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName); 162 | if (result?.Principal is WindowsPrincipal wp) 163 | { 164 | props.Items.Add("scheme", AccountOptions.WindowsAuthenticationSchemeName); 165 | 166 | var id = new ClaimsIdentity(provider); 167 | id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name)); 168 | id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name)); 169 | 170 | // add the groups as claims -- be careful if the number of groups is too large 171 | if (AccountOptions.IncludeWindowsGroups) 172 | { 173 | var wi = wp.Identity as WindowsIdentity; 174 | var groups = wi.Groups.Translate(typeof(NTAccount)); 175 | var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value)); 176 | id.AddClaims(roles); 177 | } 178 | 179 | await HttpContext.SignInAsync( 180 | IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme, 181 | new ClaimsPrincipal(id), 182 | props); 183 | return Redirect(props.RedirectUri); 184 | } 185 | else 186 | { 187 | // challenge/trigger windows auth 188 | return Challenge(AccountOptions.WindowsAuthenticationSchemeName); 189 | } 190 | } 191 | else 192 | { 193 | // start challenge and roundtrip the return URL 194 | props.Items.Add("scheme", provider); 195 | return Challenge(props, provider); 196 | } 197 | } 198 | 199 | /// 200 | /// Post processing of external authentication 201 | /// 202 | [HttpGet] 203 | public async Task ExternalLoginCallback() 204 | { 205 | // read external identity from the temporary cookie 206 | var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); 207 | if (result?.Succeeded != true) 208 | { 209 | throw new Exception("External authentication error"); 210 | } 211 | 212 | // retrieve claims of the external user 213 | var externalUser = result.Principal; 214 | var claims = externalUser.Claims.ToList(); 215 | 216 | // try to determine the unique id of the external user (issued by the provider) 217 | // the most common claim type for that are the sub claim and the NameIdentifier 218 | // depending on the external provider, some other claim type might be used 219 | var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject); 220 | if (userIdClaim == null) 221 | { 222 | userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier); 223 | } 224 | if (userIdClaim == null) 225 | { 226 | throw new Exception("Unknown userid"); 227 | } 228 | 229 | // remove the user id claim from the claims collection and move to the userId property 230 | // also set the name of the external authentication provider 231 | claims.Remove(userIdClaim); 232 | var provider = result.Properties.Items["scheme"]; 233 | var userId = userIdClaim.Value; 234 | 235 | // this is where custom logic would most likely be needed to match your users from the 236 | // external provider's authentication result, and provision the user as you see fit. 237 | // 238 | // check if the external user is already provisioned 239 | var user = _users.FindByExternalProvider(provider, userId); 240 | if (user == null) 241 | { 242 | // this sample simply auto-provisions new external user 243 | // another common approach is to start a registrations workflow first 244 | user = _users.AutoProvisionUser(provider, userId, claims); 245 | } 246 | 247 | var additionalClaims = new List(); 248 | 249 | // if the external system sent a session id claim, copy it over 250 | // so we can use it for single sign-out 251 | var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); 252 | if (sid != null) 253 | { 254 | additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); 255 | } 256 | 257 | // if the external provider issued an id_token, we'll keep it for signout 258 | AuthenticationProperties props = null; 259 | var id_token = result.Properties.GetTokenValue("id_token"); 260 | if (id_token != null) 261 | { 262 | props = new AuthenticationProperties(); 263 | props.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } }); 264 | } 265 | 266 | // issue authentication cookie for user 267 | await _events.RaiseAsync(new UserLoginSuccessEvent(provider, userId, user.SubjectId, user.Username)); 268 | await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray()); 269 | 270 | // delete temporary cookie used during external authentication 271 | await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); 272 | 273 | // validate return URL and redirect back to authorization endpoint or a local page 274 | var returnUrl = result.Properties.Items["returnUrl"]; 275 | if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl)) 276 | { 277 | return Redirect(returnUrl); 278 | } 279 | 280 | return Redirect("~/"); 281 | } 282 | 283 | /// 284 | /// Show logout page 285 | /// 286 | [HttpGet] 287 | public async Task Logout(string logoutId) 288 | { 289 | // build a model so the logout page knows what to display 290 | var vm = await _account.BuildLogoutViewModelAsync(logoutId); 291 | 292 | if (vm.ShowLogoutPrompt == false) 293 | { 294 | // if the request for logout was properly authenticated from IdentityServer, then 295 | // we don't need to show the prompt and can just log the user out directly. 296 | return await Logout(vm); 297 | } 298 | 299 | return View(vm); 300 | } 301 | 302 | /// 303 | /// Handle logout page postback 304 | /// 305 | [HttpPost] 306 | [ValidateAntiForgeryToken] 307 | public async Task Logout(LogoutInputModel model) 308 | { 309 | // build a model so the logged out page knows what to display 310 | var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId); 311 | 312 | var user = HttpContext.User; 313 | if (user?.Identity.IsAuthenticated == true) 314 | { 315 | // delete local authentication cookie 316 | await HttpContext.SignOutAsync(); 317 | 318 | // raise the logout event 319 | await _events.RaiseAsync(new UserLogoutSuccessEvent(user.GetSubjectId(), user.GetDisplayName())); 320 | } 321 | 322 | // check if we need to trigger sign-out at an upstream identity provider 323 | if (vm.TriggerExternalSignout) 324 | { 325 | // build a return URL so the upstream provider will redirect back 326 | // to us after the user has logged out. this allows us to then 327 | // complete our single sign-out processing. 328 | string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); 329 | 330 | // this triggers a redirect to the external provider for sign-out 331 | return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); 332 | } 333 | 334 | return View("LoggedOut", vm); 335 | } 336 | } 337 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/AccountOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class AccountOptions 10 | { 11 | public static bool AllowLocalLogin = true; 12 | public static bool AllowRememberLogin = true; 13 | public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); 14 | 15 | public static bool ShowLogoutPrompt = true; 16 | public static bool AutomaticRedirectAfterSignOut = false; 17 | 18 | // to enable windows authentication, the host (IIS or IIS Express) also must have 19 | // windows auth enabled. 20 | public static bool WindowsAuthenticationEnabled = true; 21 | public static bool IncludeWindowsGroups = false; 22 | // specify the Windows authentication scheme and display name 23 | public static readonly string WindowsAuthenticationSchemeName = "Windows"; 24 | 25 | public static string InvalidCredentialsErrorMessage = "Invalid username or password"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/AccountService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4.Extensions; 7 | using IdentityServer4.Services; 8 | using IdentityServer4.Stores; 9 | using Microsoft.AspNetCore.Authentication; 10 | using Microsoft.AspNetCore.Http; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | using System; 14 | 15 | namespace IdentityServer4.Quickstart.UI 16 | { 17 | public class AccountService 18 | { 19 | private readonly IClientStore _clientStore; 20 | private readonly IIdentityServerInteractionService _interaction; 21 | private readonly IHttpContextAccessor _httpContextAccessor; 22 | private readonly IAuthenticationSchemeProvider _schemeProvider; 23 | 24 | public AccountService( 25 | IIdentityServerInteractionService interaction, 26 | IHttpContextAccessor httpContextAccessor, 27 | IAuthenticationSchemeProvider schemeProvider, 28 | IClientStore clientStore) 29 | { 30 | _interaction = interaction; 31 | _httpContextAccessor = httpContextAccessor; 32 | _schemeProvider = schemeProvider; 33 | _clientStore = clientStore; 34 | } 35 | 36 | public async Task BuildLoginViewModelAsync(string returnUrl) 37 | { 38 | var context = await _interaction.GetAuthorizationContextAsync(returnUrl); 39 | if (context?.IdP != null) 40 | { 41 | // this is meant to short circuit the UI and only trigger the one external IdP 42 | return new LoginViewModel 43 | { 44 | EnableLocalLogin = false, 45 | ReturnUrl = returnUrl, 46 | Username = context?.LoginHint, 47 | ExternalProviders = new ExternalProvider[] {new ExternalProvider { AuthenticationScheme = context.IdP } } 48 | }; 49 | } 50 | 51 | var schemes = await _schemeProvider.GetAllSchemesAsync(); 52 | 53 | var providers = schemes 54 | .Where(x => x.DisplayName != null || 55 | (AccountOptions.WindowsAuthenticationEnabled && 56 | x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) 57 | ) 58 | .Select(x => new ExternalProvider 59 | { 60 | DisplayName = x.DisplayName, 61 | AuthenticationScheme = x.Name 62 | }).ToList(); 63 | 64 | var allowLocal = true; 65 | if (context?.ClientId != null) 66 | { 67 | var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); 68 | if (client != null) 69 | { 70 | allowLocal = client.EnableLocalLogin; 71 | 72 | if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) 73 | { 74 | providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); 75 | } 76 | } 77 | } 78 | 79 | return new LoginViewModel 80 | { 81 | AllowRememberLogin = AccountOptions.AllowRememberLogin, 82 | EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, 83 | ReturnUrl = returnUrl, 84 | Username = context?.LoginHint, 85 | ExternalProviders = providers.ToArray() 86 | }; 87 | } 88 | 89 | public async Task BuildLoginViewModelAsync(LoginInputModel model) 90 | { 91 | var vm = await BuildLoginViewModelAsync(model.ReturnUrl); 92 | vm.Username = model.Username; 93 | vm.RememberLogin = model.RememberLogin; 94 | return vm; 95 | } 96 | 97 | public async Task BuildLogoutViewModelAsync(string logoutId) 98 | { 99 | var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; 100 | 101 | var user = _httpContextAccessor.HttpContext.User; 102 | if (user?.Identity.IsAuthenticated != true) 103 | { 104 | // if the user is not authenticated, then just show logged out page 105 | vm.ShowLogoutPrompt = false; 106 | return vm; 107 | } 108 | 109 | var context = await _interaction.GetLogoutContextAsync(logoutId); 110 | if (context?.ShowSignoutPrompt == false) 111 | { 112 | // it's safe to automatically sign-out 113 | vm.ShowLogoutPrompt = false; 114 | return vm; 115 | } 116 | 117 | // show the logout prompt. this prevents attacks where the user 118 | // is automatically signed out by another malicious web page. 119 | return vm; 120 | } 121 | 122 | public async Task BuildLoggedOutViewModelAsync(string logoutId) 123 | { 124 | // get context information (client name, post logout redirect URI and iframe for federated signout) 125 | var logout = await _interaction.GetLogoutContextAsync(logoutId); 126 | 127 | var vm = new LoggedOutViewModel 128 | { 129 | AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, 130 | PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, 131 | ClientName = logout?.ClientId, 132 | SignOutIframeUrl = logout?.SignOutIFrameUrl, 133 | LogoutId = logoutId 134 | }; 135 | 136 | var user = _httpContextAccessor.HttpContext.User; 137 | if (user?.Identity.IsAuthenticated == true) 138 | { 139 | var idp = user.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; 140 | if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) 141 | { 142 | var providerSupportsSignout = await _httpContextAccessor.HttpContext.GetSchemeSupportsSignOutAsync(idp); 143 | if (providerSupportsSignout) 144 | { 145 | if (vm.LogoutId == null) 146 | { 147 | // if there's no current logout context, we need to create one 148 | // this captures necessary info from the current logged in user 149 | // before we signout and redirect away to the external IdP for signout 150 | vm.LogoutId = await _interaction.CreateLogoutContextAsync(); 151 | } 152 | 153 | vm.ExternalAuthenticationScheme = idp; 154 | } 155 | } 156 | } 157 | 158 | return vm; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/ExternalProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ExternalProvider 8 | { 9 | public string DisplayName { get; set; } 10 | public string AuthenticationScheme { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/LoggedOutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class LoggedOutViewModel 8 | { 9 | public string PostLogoutRedirectUri { get; set; } 10 | public string ClientName { get; set; } 11 | public string SignOutIframeUrl { get; set; } 12 | 13 | public bool AutomaticRedirectAfterSignOut { get; set; } 14 | 15 | public string LogoutId { get; set; } 16 | public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; 17 | public string ExternalAuthenticationScheme { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/LoginInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class LoginInputModel 10 | { 11 | [Required] 12 | public string Username { get; set; } 13 | [Required] 14 | public string Password { get; set; } 15 | public bool RememberLogin { get; set; } 16 | public string ReturnUrl { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace IdentityServer4.Quickstart.UI 10 | { 11 | public class LoginViewModel : LoginInputModel 12 | { 13 | public bool AllowRememberLogin { get; set; } 14 | public bool EnableLocalLogin { get; set; } 15 | 16 | public IEnumerable ExternalProviders { get; set; } 17 | public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); 18 | 19 | public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; 20 | public string ExternalLoginScheme => ExternalProviders?.SingleOrDefault()?.AuthenticationScheme; 21 | } 22 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/LogoutInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class LogoutInputModel 8 | { 9 | public string LogoutId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Account/LogoutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class LogoutViewModel : LogoutInputModel 8 | { 9 | public bool ShowLogoutPrompt { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Consent/ConsentController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using IdentityServer4.Stores; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Logging; 9 | using System.Threading.Tasks; 10 | 11 | namespace IdentityServer4.Quickstart.UI 12 | { 13 | /// 14 | /// This controller processes the consent UI 15 | /// 16 | [SecurityHeaders] 17 | public class ConsentController : Controller 18 | { 19 | private readonly ConsentService _consent; 20 | 21 | public ConsentController( 22 | IIdentityServerInteractionService interaction, 23 | IClientStore clientStore, 24 | IResourceStore resourceStore, 25 | ILogger logger) 26 | { 27 | _consent = new ConsentService(interaction, clientStore, resourceStore, logger); 28 | } 29 | 30 | /// 31 | /// Shows the consent screen 32 | /// 33 | /// 34 | /// 35 | [HttpGet] 36 | public async Task Index(string returnUrl) 37 | { 38 | var vm = await _consent.BuildViewModelAsync(returnUrl); 39 | if (vm != null) 40 | { 41 | return View("Index", vm); 42 | } 43 | 44 | return View("Error"); 45 | } 46 | 47 | /// 48 | /// Handles the consent screen postback 49 | /// 50 | [HttpPost] 51 | [ValidateAntiForgeryToken] 52 | public async Task Index(ConsentInputModel model) 53 | { 54 | var result = await _consent.ProcessConsent(model); 55 | 56 | if (result.IsRedirect) 57 | { 58 | return Redirect(result.RedirectUri); 59 | } 60 | 61 | if (result.HasValidationError) 62 | { 63 | ModelState.AddModelError("", result.ValidationError); 64 | } 65 | 66 | if (result.ShowView) 67 | { 68 | return View("Index", result.ViewModel); 69 | } 70 | 71 | return View("Error"); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Consent/ConsentInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class ConsentInputModel 10 | { 11 | public string Button { get; set; } 12 | public IEnumerable ScopesConsented { get; set; } 13 | public bool RememberConsent { get; set; } 14 | public string ReturnUrl { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Consent/ConsentOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ConsentOptions 8 | { 9 | public static bool EnableOfflineAccess = true; 10 | public static string OfflineAccessDisplayName = "Offline Access"; 11 | public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; 12 | 13 | public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; 14 | public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Consent/ConsentService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | using IdentityServer4.Services; 7 | using IdentityServer4.Stores; 8 | using Microsoft.Extensions.Logging; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace IdentityServer4.Quickstart.UI 13 | { 14 | public class ConsentService 15 | { 16 | private readonly IClientStore _clientStore; 17 | private readonly IResourceStore _resourceStore; 18 | private readonly IIdentityServerInteractionService _interaction; 19 | private readonly ILogger _logger; 20 | 21 | public ConsentService( 22 | IIdentityServerInteractionService interaction, 23 | IClientStore clientStore, 24 | IResourceStore resourceStore, 25 | ILogger logger) 26 | { 27 | _interaction = interaction; 28 | _clientStore = clientStore; 29 | _resourceStore = resourceStore; 30 | _logger = logger; 31 | } 32 | 33 | public async Task ProcessConsent(ConsentInputModel model) 34 | { 35 | var result = new ProcessConsentResult(); 36 | 37 | ConsentResponse grantedConsent = null; 38 | 39 | // user clicked 'no' - send back the standard 'access_denied' response 40 | if (model.Button == "no") 41 | { 42 | grantedConsent = ConsentResponse.Denied; 43 | } 44 | // user clicked 'yes' - validate the data 45 | else if (model.Button == "yes" && model != null) 46 | { 47 | // if the user consented to some scope, build the response model 48 | if (model.ScopesConsented != null && model.ScopesConsented.Any()) 49 | { 50 | var scopes = model.ScopesConsented; 51 | if (ConsentOptions.EnableOfflineAccess == false) 52 | { 53 | scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); 54 | } 55 | 56 | grantedConsent = new ConsentResponse 57 | { 58 | RememberConsent = model.RememberConsent, 59 | ScopesConsented = scopes.ToArray() 60 | }; 61 | } 62 | else 63 | { 64 | result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; 65 | } 66 | } 67 | else 68 | { 69 | result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; 70 | } 71 | 72 | if (grantedConsent != null) 73 | { 74 | // validate return url is still valid 75 | var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 76 | if (request == null) return result; 77 | 78 | // communicate outcome of consent back to identityserver 79 | await _interaction.GrantConsentAsync(request, grantedConsent); 80 | 81 | // indicate that's it ok to redirect back to authorization endpoint 82 | result.RedirectUri = model.ReturnUrl; 83 | } 84 | else 85 | { 86 | // we need to redisplay the consent UI 87 | result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); 88 | } 89 | 90 | return result; 91 | } 92 | 93 | public async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) 94 | { 95 | var request = await _interaction.GetAuthorizationContextAsync(returnUrl); 96 | if (request != null) 97 | { 98 | var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); 99 | if (client != null) 100 | { 101 | var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); 102 | if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) 103 | { 104 | return CreateConsentViewModel(model, returnUrl, request, client, resources); 105 | } 106 | else 107 | { 108 | _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); 109 | } 110 | } 111 | else 112 | { 113 | _logger.LogError("Invalid client id: {0}", request.ClientId); 114 | } 115 | } 116 | else 117 | { 118 | _logger.LogError("No consent request matching request: {0}", returnUrl); 119 | } 120 | 121 | return null; 122 | } 123 | 124 | private ConsentViewModel CreateConsentViewModel( 125 | ConsentInputModel model, string returnUrl, 126 | AuthorizationRequest request, 127 | Client client, Resources resources) 128 | { 129 | var vm = new ConsentViewModel(); 130 | vm.RememberConsent = model?.RememberConsent ?? true; 131 | vm.ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(); 132 | 133 | vm.ReturnUrl = returnUrl; 134 | 135 | vm.ClientName = client.ClientName ?? client.ClientId; 136 | vm.ClientUrl = client.ClientUri; 137 | vm.ClientLogoUrl = client.LogoUri; 138 | vm.AllowRememberConsent = client.AllowRememberConsent; 139 | 140 | vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 141 | vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 142 | if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess) 143 | { 144 | vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] { 145 | GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null) 146 | }); 147 | } 148 | 149 | return vm; 150 | } 151 | 152 | public ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) 153 | { 154 | return new ScopeViewModel 155 | { 156 | Name = identity.Name, 157 | DisplayName = identity.DisplayName, 158 | Description = identity.Description, 159 | Emphasize = identity.Emphasize, 160 | Required = identity.Required, 161 | Checked = check || identity.Required 162 | }; 163 | } 164 | 165 | public ScopeViewModel CreateScopeViewModel(Scope scope, bool check) 166 | { 167 | return new ScopeViewModel 168 | { 169 | Name = scope.Name, 170 | DisplayName = scope.DisplayName, 171 | Description = scope.Description, 172 | Emphasize = scope.Emphasize, 173 | Required = scope.Required, 174 | Checked = check || scope.Required 175 | }; 176 | } 177 | 178 | private ScopeViewModel GetOfflineAccessScope(bool check) 179 | { 180 | return new ScopeViewModel 181 | { 182 | Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, 183 | DisplayName = ConsentOptions.OfflineAccessDisplayName, 184 | Description = ConsentOptions.OfflineAccessDescription, 185 | Emphasize = true, 186 | Checked = check 187 | }; 188 | } 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Consent/ConsentViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class ConsentViewModel : ConsentInputModel 10 | { 11 | public string ClientName { get; set; } 12 | public string ClientUrl { get; set; } 13 | public string ClientLogoUrl { get; set; } 14 | public bool AllowRememberConsent { get; set; } 15 | 16 | public IEnumerable IdentityScopes { get; set; } 17 | public IEnumerable ResourceScopes { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ProcessConsentResult 8 | { 9 | public bool IsRedirect => RedirectUri != null; 10 | public string RedirectUri { get; set; } 11 | 12 | public bool ShowView => ViewModel != null; 13 | public ConsentViewModel ViewModel { get; set; } 14 | 15 | public bool HasValidationError => ValidationError != null; 16 | public string ValidationError { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Consent/ScopeViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ScopeViewModel 8 | { 9 | public string Name { get; set; } 10 | public string DisplayName { get; set; } 11 | public string Description { get; set; } 12 | public bool Emphasize { get; set; } 13 | public bool Required { get; set; } 14 | public bool Checked { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Grants/GrantsController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using IdentityServer4.Stores; 7 | using Microsoft.AspNetCore.Mvc; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using Microsoft.AspNetCore.Authorization; 12 | 13 | namespace IdentityServer4.Quickstart.UI 14 | { 15 | /// 16 | /// This sample controller allows a user to revoke grants given to clients 17 | /// 18 | [SecurityHeaders] 19 | [Authorize] 20 | public class GrantsController : Controller 21 | { 22 | private readonly IIdentityServerInteractionService _interaction; 23 | private readonly IClientStore _clients; 24 | private readonly IResourceStore _resources; 25 | 26 | public GrantsController(IIdentityServerInteractionService interaction, 27 | IClientStore clients, 28 | IResourceStore resources) 29 | { 30 | _interaction = interaction; 31 | _clients = clients; 32 | _resources = resources; 33 | } 34 | 35 | /// 36 | /// Show list of grants 37 | /// 38 | [HttpGet] 39 | public async Task Index() 40 | { 41 | return View("Index", await BuildViewModelAsync()); 42 | } 43 | 44 | /// 45 | /// Handle postback to revoke a client 46 | /// 47 | [HttpPost] 48 | [ValidateAntiForgeryToken] 49 | public async Task Revoke(string clientId) 50 | { 51 | await _interaction.RevokeUserConsentAsync(clientId); 52 | return RedirectToAction("Index"); 53 | } 54 | 55 | private async Task BuildViewModelAsync() 56 | { 57 | var grants = await _interaction.GetAllUserConsentsAsync(); 58 | 59 | var list = new List(); 60 | foreach(var grant in grants) 61 | { 62 | var client = await _clients.FindClientByIdAsync(grant.ClientId); 63 | if (client != null) 64 | { 65 | var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); 66 | 67 | var item = new GrantViewModel() 68 | { 69 | ClientId = client.ClientId, 70 | ClientName = client.ClientName ?? client.ClientId, 71 | ClientLogoUrl = client.LogoUri, 72 | ClientUrl = client.ClientUri, 73 | Created = grant.CreationTime, 74 | Expires = grant.Expiration, 75 | IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), 76 | ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() 77 | }; 78 | 79 | list.Add(item); 80 | } 81 | } 82 | 83 | return new GrantsViewModel 84 | { 85 | Grants = list 86 | }; 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Grants/GrantsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace IdentityServer4.Quickstart.UI 5 | { 6 | public class GrantsViewModel 7 | { 8 | public IEnumerable Grants { get; set; } 9 | } 10 | 11 | public class GrantViewModel 12 | { 13 | public string ClientId { get; set; } 14 | public string ClientName { get; set; } 15 | public string ClientUrl { get; set; } 16 | public string ClientLogoUrl { get; set; } 17 | public DateTime Created { get; set; } 18 | public DateTime? Expires { get; set; } 19 | public IEnumerable IdentityGrantNames { get; set; } 20 | public IEnumerable ApiGrantNames { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Home/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class ErrorViewModel 10 | { 11 | public ErrorMessage Error { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/Home/HomeController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using Microsoft.AspNetCore.Mvc; 7 | using System.Threading.Tasks; 8 | 9 | namespace IdentityServer4.Quickstart.UI 10 | { 11 | [SecurityHeaders] 12 | public class HomeController : Controller 13 | { 14 | private readonly IIdentityServerInteractionService _interaction; 15 | 16 | public HomeController(IIdentityServerInteractionService interaction) 17 | { 18 | _interaction = interaction; 19 | } 20 | 21 | public IActionResult Index() 22 | { 23 | return View(); 24 | } 25 | 26 | /// 27 | /// Shows the error page 28 | /// 29 | public async Task Error(string errorId) 30 | { 31 | var vm = new ErrorViewModel(); 32 | 33 | // retrieve error details from identityserver 34 | var message = await _interaction.GetErrorContextAsync(errorId); 35 | if (message != null) 36 | { 37 | vm.Error = message; 38 | } 39 | 40 | return View("Error", vm); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Quickstart/SecurityHeadersAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.Filters; 7 | 8 | namespace IdentityServer4.Quickstart.UI 9 | { 10 | public class SecurityHeadersAttribute : ActionFilterAttribute 11 | { 12 | public override void OnResultExecuting(ResultExecutingContext context) 13 | { 14 | var result = context.Result; 15 | if (result is ViewResult) 16 | { 17 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options 18 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) 19 | { 20 | context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); 21 | } 22 | 23 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 24 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) 25 | { 26 | context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); 27 | } 28 | 29 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 30 | var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; 31 | // also consider adding upgrade-insecure-requests once you have HTTPS in place for production 32 | //csp += "upgrade-insecure-requests;"; 33 | // also an example if you need client images to be displayed from twitter 34 | // csp += "img-src 'self' https://pbs.twimg.com;"; 35 | 36 | // once for standards compliant browsers 37 | if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) 38 | { 39 | context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); 40 | } 41 | // and once again for IE 42 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) 43 | { 44 | context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); 45 | } 46 | 47 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy 48 | var referrer_policy = "no-referrer"; 49 | if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) 50 | { 51 | context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Configuration; 10 | using IdentityServer4.Test; 11 | using System.Collections.Generic; 12 | using IdentityModel; 13 | using System.Security.Claims; 14 | using IdentityServer4.Models; 15 | using IdentityServer4.Quickstart.UI; 16 | 17 | namespace IdentityServer 18 | { 19 | public class Startup 20 | { 21 | public IHostingEnvironment Environment { get; } 22 | public IConfiguration Configuration { get; } 23 | 24 | public Startup(IHostingEnvironment environment, IConfiguration configuration) 25 | { 26 | Environment = environment; 27 | Configuration = configuration; 28 | } 29 | 30 | public void ConfigureServices(IServiceCollection services) 31 | { 32 | services.AddMvc(); 33 | 34 | var builder = services.AddIdentityServer(options => 35 | { 36 | options.IssuerUri = "MyIdentityServer"; 37 | options.Events.RaiseErrorEvents = true; 38 | options.Events.RaiseInformationEvents = true; 39 | options.Events.RaiseFailureEvents = true; 40 | options.Events.RaiseSuccessEvents = true; 41 | }) 42 | .AddTestUsers(new List { 43 | new TestUser{SubjectId = "1", Username = "zerokoll", Password = "test", 44 | Claims = 45 | { 46 | new Claim(JwtClaimTypes.Name, "Chris Klug"), 47 | new Claim(JwtClaimTypes.GivenName, "Chris"), 48 | new Claim(JwtClaimTypes.FamilyName, "Klug"), 49 | new Claim(JwtClaimTypes.Email, "chris@59north.com"), 50 | } 51 | } 52 | }); 53 | 54 | // in-memory, json config 55 | builder.AddInMemoryIdentityResources(Configuration.GetSection("IdentityResources")); 56 | builder.AddInMemoryApiResources(Configuration.GetSection("ApiResources")); 57 | builder.AddInMemoryClients(Configuration.GetSection("clients")); 58 | 59 | if (Environment.IsDevelopment()) 60 | { 61 | builder.AddDeveloperSigningCredential(); 62 | } 63 | else 64 | { 65 | throw new Exception("need to configure key material"); 66 | } 67 | 68 | AccountOptions.AllowRememberLogin = false; 69 | AccountOptions.AutomaticRedirectAfterSignOut = true; 70 | AccountOptions.ShowLogoutPrompt = false; 71 | AccountOptions.WindowsAuthenticationEnabled = false; 72 | } 73 | 74 | public void Configure(IApplicationBuilder app) 75 | { 76 | if (Environment.IsDevelopment()) 77 | { 78 | app.UseDeveloperExceptionPage(); 79 | } 80 | 81 | app.UseIdentityServer(); 82 | app.UseStaticFiles(); 83 | app.UseMvcWithDefaultRoute(); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Account/LoggedOut.cshtml: -------------------------------------------------------------------------------- 1 | @model LoggedOutViewModel 2 | 3 | @{ 4 | // set this so the layout rendering sees an anonymous user 5 | ViewData["signed-out"] = true; 6 | } 7 | 8 | 27 | 28 | @section scripts 29 | { 30 | @if (Model.AutomaticRedirectAfterSignOut) 31 | { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginViewModel 2 | 3 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @model LogoutViewModel 2 | 3 |
4 | 7 | 8 |
9 |
10 |

Would you like to logout of IdentityServer?

11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
-------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Consent/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model ConsentViewModel 2 | 3 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Consent/_ScopeListItem.cshtml: -------------------------------------------------------------------------------- 1 | @model ScopeViewModel 2 | 3 |
  • 4 | 24 | @if (Model.Required) 25 | { 26 | (required) 27 | } 28 | @if (Model.Description != null) 29 | { 30 | 33 | } 34 |
  • -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Grants/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model GrantsViewModel 2 | 3 |
    4 | 12 | 13 | @if (Model.Grants.Any() == false) 14 | { 15 |
    16 |
    17 |
    18 | You have not given access to any applications 19 |
    20 |
    21 |
    22 | } 23 | else 24 | { 25 | foreach (var grant in Model.Grants) 26 | { 27 |
    28 |
    29 | @if (grant.ClientLogoUrl != null) 30 | { 31 | 32 | } 33 |
    34 |
    35 |
    @grant.ClientName
    36 |
    37 | Created: @grant.Created.ToString("yyyy-MM-dd") 38 |
    39 | @if (grant.Expires.HasValue) 40 | { 41 |
    42 | Expires: @grant.Expires.Value.ToString("yyyy-MM-dd") 43 |
    44 | } 45 | @if (grant.IdentityGrantNames.Any()) 46 | { 47 |
    48 |
    Identity Grants
    49 |
      50 | @foreach (var name in grant.IdentityGrantNames) 51 | { 52 |
    • @name
    • 53 | } 54 |
    55 |
    56 | } 57 | @if (grant.ApiGrantNames.Any()) 58 | { 59 |
    60 |
    API Grants
    61 |
      62 | @foreach (var name in grant.ApiGrantNames) 63 | { 64 |
    • @name
    • 65 | } 66 |
    67 |
    68 | } 69 |
    70 |
    71 |
    72 | 73 | 74 |
    75 |
    76 |
    77 | } 78 | } 79 |
    -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 
    2 | 11 | 12 |
    13 |
    14 |

    15 | IdentityServer publishes a 16 | discovery document 17 | where you can find metadata and links to all the endpoints, key material, etc. 18 |

    19 |
    20 |
    21 |

    22 | Click here to manage your stored grants. 23 |

    24 |
    25 |
    26 |
    27 |
    28 |

    29 | Here are links to the 30 | source code repository, 31 | and ready to use samples. 32 |

    33 |
    34 |
    35 |
    36 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Hosting 2 | @model ErrorViewModel 3 | @inject IHostingEnvironment host 4 | 5 | @{ 6 | var error = Model?.Error?.Error; 7 | var errorDescription = host.IsDevelopment() ? Model?.Error?.ErrorDescription : null; 8 | var request_id = Model?.Error?.RequestId; 9 | } 10 | 11 |
    12 | 15 | 16 |
    17 |
    18 |
    19 | Sorry, there was an error 20 | 21 | @if (error != null) 22 | { 23 | 24 | 25 | : @error 26 | 27 | 28 | 29 | if (errorDescription != null) 30 | { 31 |
    @errorDescription
    32 | } 33 | } 34 |
    35 | 36 | @if (request_id != null) 37 | { 38 |
    Request Id: @request_id
    39 | } 40 |
    41 |
    42 |
    43 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer4.Extensions 2 | @{ 3 | string name = null; 4 | if (!true.Equals(ViewData["signed-out"])) 5 | { 6 | name = Context.User?.GetDisplayName(); 7 | } 8 | } 9 | 10 | 11 | 12 | 13 | 14 | 15 | IdentityServer4 16 | 17 | 18 | 19 | 20 | 21 | 22 | 52 | 53 |
    54 | @RenderBody() 55 |
    56 | 57 | 58 | 59 | @RenderSection("scripts", required: false) 60 | 61 | 62 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/Shared/_ValidationSummary.cshtml: -------------------------------------------------------------------------------- 1 | @if (ViewContext.ModelState.IsValid == false) 2 | { 3 |
    4 | Error 5 |
    6 |
    7 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer4.Quickstart.UI 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IdentityResources": [ 3 | { 4 | "Name": "openid", 5 | "DisplayName": "Your user identifier", 6 | "Required": true, 7 | "UserClaims": [ 8 | "sub" 9 | ] 10 | }, 11 | { 12 | "Name": "profile", 13 | "DisplayName": "User profile", 14 | "Description": "Your user profile information (first name, last name, etc.)", 15 | "Emphasize": true, 16 | "UserClaims": [ 17 | "name", 18 | "family_name", 19 | "given_name", 20 | "middle_name", 21 | "preferred_username", 22 | "profile", 23 | "picture", 24 | "website", 25 | "gender", 26 | "birthdate", 27 | "zoneinfo", 28 | "locale", 29 | "updated_at", 30 | "email" 31 | ] 32 | } 33 | ], 34 | 35 | "ApiResources": [ 36 | { 37 | "Name": "api1", 38 | "DisplayName": "My API #1", 39 | "Scopes": [ 40 | { 41 | "Name": "api1" 42 | } 43 | ] 44 | } 45 | ], 46 | 47 | "Clients": [ 48 | { 49 | "ClientId": "client", 50 | "ClientName": "Client Credentials Client", 51 | 52 | // 511536EF-F270-4058-80CA-1C89C192F69A 53 | "ClientSecrets": [ { "Value": "fU7fRb+g6YdlniuSqviOLWNkda1M/MuPtH6zNI9inF8=" } ], 54 | "AllowedGrantTypes": [ "client_credentials" ], 55 | "AllowedScopes": [ "api1" ] 56 | }, 57 | { 58 | "ClientId": "mvc", 59 | "ClientName": "MVC Client", 60 | 61 | // 49C1A7E1-0C79-4A89-A3D6-A37998FB86B0 62 | "ClientSecrets": [ { "Value": "o90IbCACXKUkunXoa18cODcLKnQTbjOo5ihEw9j58+8=" } ], 63 | "AllowedGrantTypes": [ "implicit" ], 64 | "AllowedScopes": [ "openid", "profile", "api1" ], 65 | "AllowOfflineAccess": true, 66 | 67 | "RedirectUris": [ "http://localhost:3003/signin-oidc" ], 68 | "FrontChannelLogoutUris": [ "http://localhost:3003/signout-oidc" ], 69 | "PostLogoutRedirectUris": [ "http://localhost:3003/signout-callback-oidc" ], 70 | 71 | "RequireConsent": false 72 | }, 73 | { 74 | "ClientId": "spa", 75 | "ClientName": "SPA Client", 76 | 77 | "AllowedGrantTypes": [ "implicit" ], 78 | "AllowedScopes": [ "openid", "profile", "api1" ], 79 | "AllowAccessTokensViaBrowser": true, 80 | 81 | "RedirectUris": [ 82 | "http://localhost:3003/callback.html" 83 | ], 84 | "FrontChannelLogoutUris": [ "http://localhost:3003/" ], 85 | "PostLogoutRedirectUris": [ "http://localhost:3003/signoutcallback.html" ], 86 | "AllowedCorsOrigins": [ "http://localhost:3003" ], 87 | 88 | "RequireConsent": false 89 | } 90 | ] 91 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/tempkey.rsa: -------------------------------------------------------------------------------- 1 | {"KeyId":"1cbba612bdaa3045b2740a67cfda94a2","Parameters":{"D":"f6Fanaw1nEeUoddx/JMo0GjTUwHpfqZD3i1BSyyhRpMsSo1PmqCiB2v/K2G16Q2Rf/pKJdu0hmaKHemLWTJx4ugpsBxLs4NScm1PNrlWwAa9Eyp7T7YfzkZ1rQQP7jri5w0xFZEbQriThSa8Ski+/pu7WCCO5Lt/RIk9pRvNWAlt4+6Z9bcBYKXBfnlrvY5LS40egn5XJprQIx2fP8kpfpyliYNykewj01UP47puigzT3GlUtCTYHNw3CUga9QjZtL8ctpbEZZfFduzGEm7ca1JWSpBcQz4jyYC7ZBelinKDpHcajdfCeqNkxLmGBelMlXkCPqbF4es5li2jnZ/JfQ==","DP":"SHqtHwIyTyqozr9xiGePAmWwy76a2ePbIVc36l/MiNM7Tn5/X8EL3q9wUWz7ubejmuhVn1kf9MmuMe203aCrRAJfCydUSxAcwW+t2Va4BNa2zoBAwsM9JSGGsG+7VLjpuAdEiP8TuIo4nEtULhmASTSHZKcnenjs/xza/dftfkk=","DQ":"TDseDVUYY2dGj+yRLbYLQNKIq3HeROvU6FHdlVMzW+Rs98rhPppWTmO9ZBZn2kruqePoQrACKRGqxP9BIynlo+myBj6+lSxEexqg7JAmDJyGRPeVY6ISDIZVaywTnTTW6umB7gY6hezsBVESkX0nJtWzC3KLrRs5A0Pm9u16DP0=","Exponent":"AQAB","InverseQ":"K3HI35qK6n2ANPUo+TU/9UqhPyuffwaCb3fLfLbmOX8szUoFEYHl2p+zuspphMWyPiwW7g9ITlVMJ20LMo1VXbs7EyxScI44W8rpycjS9/tDyatY73Uv6+IrIm4XtAF2cotTl/vxaYlNCjtVoNeKRQAPpq2KL/0SNCle+brDCiM=","Modulus":"qxzfcm1rApPdtzIZ9xJaOy0YKEoYewwS8HUN9XZ36Zvk5wByKok+fkulcEp/bjhlFQPUMtRaBkTegwRxPPkmg8NWeC2vwf0mnxvtUIg7f9ka+aqI5WNLzr4CJPjIe/FnupQyemZolPXQuA+NCvdprIavTWsAm+hJcZvZQi/+gwpAotO0o/aMKpa9ZBJH54VpbL+kBResJa3A73X8NutsvtrbRoeTw4EFMUv5wt2UoS4RXnqn6JB7E+srNL9THnYECNcD62pQAIV1mewwEGAAd08tGNg5q8i5DuG4fwMBJcK4gZiu+QzHA7h+g2RQnSlQvxnSf8wAUdRI8eek/2SD4Q==","P":"xTqxD+/bcvqD0f+T6wH9+tzfJiHUTAIYHs7R+FgAF3elilRq9NKAfHFByMh74hpq6r2M27gNadgmY7QM4DaEc0iGgAvmLLDJjRQwOd9yHPvUWNoxm52HFUmN0WI2ua2VddPUdusWvjP4E24VcbXqr09NWj/9ouPj2oTZH/ZBoOs=","Q":"3hntwBNd8WIaKrDe0RRWTJcnd+oTyfE5yyIv1fIIOrj4g496c5/R2QKGJ7GnX8oz0JgSd2kZnny7W8Zv+B0NkFLrfbihA8nBTq1DBhbtjIlsBVjpNOolLS+pheDNga2atTufTaRG4acGMaH+QPVgqyGvks7NWfbUE1uYa9l/m2M="}} -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | .navbar-header { 5 | position: relative; 6 | top: -4px; 7 | } 8 | .navbar-brand > .icon-banner { 9 | position: relative; 10 | top: -2px; 11 | display: inline; 12 | } 13 | .icon { 14 | position: relative; 15 | top: -10px; 16 | } 17 | .logged-out iframe { 18 | display: none; 19 | width: 0; 20 | height: 0; 21 | } 22 | .page-consent .client-logo { 23 | float: left; 24 | } 25 | .page-consent .client-logo img { 26 | width: 80px; 27 | height: 80px; 28 | } 29 | .page-consent .consent-buttons { 30 | margin-top: 25px; 31 | } 32 | .page-consent .consent-form .consent-scopecheck { 33 | display: inline-block; 34 | margin-right: 5px; 35 | } 36 | .page-consent .consent-form .consent-description { 37 | margin-left: 25px; 38 | } 39 | .page-consent .consent-form .consent-description label { 40 | font-weight: normal; 41 | } 42 | .page-consent .consent-form .consent-remember { 43 | padding-left: 16px; 44 | } 45 | .grants .page-header { 46 | margin-bottom: 10px; 47 | } 48 | .grants .grant { 49 | margin-top: 20px; 50 | padding-bottom: 20px; 51 | border-bottom: 1px solid lightgray; 52 | } 53 | .grants .grant img { 54 | width: 100px; 55 | height: 100px; 56 | } 57 | .grants .grant .clientname { 58 | font-size: 140%; 59 | font-weight: bold; 60 | } 61 | .grants .grant .granttype { 62 | font-size: 120%; 63 | font-weight: bold; 64 | } 65 | .grants .grant .created { 66 | font-size: 120%; 67 | font-weight: bold; 68 | } 69 | .grants .grant .expires { 70 | font-size: 120%; 71 | font-weight: bold; 72 | } 73 | .grants .grant li { 74 | list-style-type: none; 75 | display: inline; 76 | } 77 | .grants .grant li:after { 78 | content: ', '; 79 | } 80 | .grants .grant li:last-child:after { 81 | content: ''; 82 | } -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/css/site.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | 5 | .navbar-header { 6 | position: relative; 7 | top: -4px; 8 | } 9 | 10 | .navbar-brand > .icon-banner { 11 | position: relative; 12 | top: -2px; 13 | display: inline; 14 | } 15 | 16 | .icon { 17 | position: relative; 18 | top: -10px; 19 | } 20 | 21 | .logged-out iframe { 22 | display: none; 23 | width: 0; 24 | height: 0; 25 | } 26 | 27 | .page-consent { 28 | .client-logo { 29 | float: left; 30 | 31 | img { 32 | width: 80px; 33 | height: 80px; 34 | } 35 | } 36 | 37 | .consent-buttons { 38 | margin-top: 25px; 39 | } 40 | 41 | .consent-form { 42 | .consent-scopecheck { 43 | display: inline-block; 44 | margin-right: 5px; 45 | } 46 | 47 | .consent-scopecheck[disabled] { 48 | //visibility:hidden; 49 | } 50 | 51 | .consent-description { 52 | margin-left: 25px; 53 | 54 | label { 55 | font-weight: normal; 56 | } 57 | } 58 | 59 | .consent-remember { 60 | padding-left: 16px; 61 | } 62 | } 63 | } 64 | 65 | .grants { 66 | .page-header { 67 | margin-bottom: 10px; 68 | } 69 | 70 | .grant { 71 | margin-top: 20px; 72 | padding-bottom: 20px; 73 | border-bottom: 1px solid lightgray; 74 | 75 | img { 76 | width: 100px; 77 | height: 100px; 78 | } 79 | 80 | .clientname { 81 | font-size: 140%; 82 | font-weight: bold; 83 | } 84 | 85 | .granttype { 86 | font-size: 120%; 87 | font-weight: bold; 88 | } 89 | 90 | .created { 91 | font-size: 120%; 92 | font-weight: bold; 93 | } 94 | 95 | .expires { 96 | font-size: 120%; 97 | font-weight: bold; 98 | } 99 | 100 | li { 101 | list-style-type: none; 102 | display: inline; 103 | 104 | &:after { 105 | content: ', '; 106 | } 107 | 108 | &:last-child:after { 109 | content: ''; 110 | } 111 | 112 | .displayname { 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{margin-top:65px;}.navbar-header{position:relative;top:-4px;}.navbar-brand>.icon-banner{position:relative;top:-2px;display:inline;}.icon{position:relative;top:-10px;}.logged-out iframe{display:none;width:0;height:0;}.page-consent .client-logo{float:left;}.page-consent .client-logo img{width:80px;height:80px;}.page-consent .consent-buttons{margin-top:25px;}.page-consent .consent-form .consent-scopecheck{display:inline-block;margin-right:5px;}.page-consent .consent-form .consent-description{margin-left:25px;}.page-consent .consent-form .consent-description label{font-weight:normal;}.page-consent .consent-form .consent-remember{padding-left:16px;}.grants .page-header{margin-bottom:10px;}.grants .grant{margin-top:20px;padding-bottom:20px;border-bottom:1px solid #d3d3d3;}.grants .grant img{width:100px;height:100px;}.grants .grant .clientname{font-size:140%;font-weight:bold;}.grants .grant .granttype{font-size:120%;font-weight:bold;}.grants .grant .created{font-size:120%;font-weight:bold;}.grants .grant .expires{font-size:120%;font-weight:bold;}.grants .grant li{list-style-type:none;display:inline;}.grants .grant li:after{content:', ';}.grants .grant li:last-child:after{content:'';} -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/IdentityServer/wwwroot/favicon.ico -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/IdentityServer/wwwroot/icon.jpg -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/IdentityServer/wwwroot/icon.png -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/js/signout-redirect.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("load", function () { 2 | var a = document.querySelector("a.PostLogoutRedirectUri"); 3 | if (a) { 4 | window.location = a.href; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /IdentityServer/IdentityServer/wwwroot/lib/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /IdentityServer/Web/Controllers/Api/ApiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.JwtBearer; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | using System.Security.Claims; 5 | 6 | namespace Web.Controllers.Api 7 | { 8 | [Route("api")] 9 | public class ApiController : Controller 10 | { 11 | [Route("user")] 12 | [HttpGet] 13 | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 14 | public IActionResult GetUser() 15 | { 16 | return Ok(new 17 | { 18 | id = User.FindFirstValue(ClaimTypes.NameIdentifier) 19 | }); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /IdentityServer/Web/Controllers/AuthController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Authentication; 4 | using Microsoft.AspNetCore.Authentication.OpenIdConnect; 5 | 6 | namespace Web.Controllers 7 | { 8 | [Route("auth")] 9 | public class AuthController : Controller 10 | { 11 | [Route("login")] 12 | public IActionResult LogIn() 13 | { 14 | return Challenge(new AuthenticationProperties { RedirectUri = "/userinfo" }); 15 | } 16 | 17 | [Route("logout")] 18 | [ValidateAntiForgeryToken] 19 | [HttpPost] 20 | public async Task LogOut() 21 | { 22 | await HttpContext.SignOutAsync(); 23 | await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme); 24 | } 25 | 26 | [Route("accessdenied")] 27 | public IActionResult AccessDenied() 28 | { 29 | return View(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /IdentityServer/Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace Web.Controllers 5 | { 6 | public class HomeController : Controller 7 | { 8 | [Route("")] 9 | public IActionResult Index() 10 | { 11 | return View(); 12 | } 13 | 14 | [Route("userinfo")] 15 | [Authorize] 16 | public IActionResult UserInformation() 17 | { 18 | return View(); 19 | } 20 | 21 | [Route("spa")] 22 | public IActionResult Spa() 23 | { 24 | return View(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /IdentityServer/Web/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace Web 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /IdentityServer/Web/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.Cookies; 2 | using Microsoft.AspNetCore.Authentication.OpenIdConnect; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Web 8 | { 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | services.AddMvc(); 14 | 15 | services.AddAuthentication(options => { 16 | options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; 17 | options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme; 18 | options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; 19 | options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; 20 | options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme; 21 | }) 22 | .AddOpenIdConnect(options => { 23 | options.Authority = "http://localhost:5000"; 24 | options.RequireHttpsMetadata = false; 25 | options.ClientId = "mvc"; 26 | options.SignedOutRedirectUri = "/"; 27 | options.SaveTokens = true; 28 | options.Events = new OpenIdConnectEvents 29 | { 30 | OnRemoteFailure = context => 31 | { 32 | context.Response.Redirect("/"); 33 | context.HandleResponse(); 34 | return Task.CompletedTask; 35 | } 36 | }; 37 | }) 38 | .AddCookie() 39 | .AddJwtBearer(options => { 40 | options.Authority = "http://localhost:5000"; 41 | options.RequireHttpsMetadata = false; 42 | options.Audience = "api1"; 43 | }); 44 | } 45 | 46 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 47 | { 48 | if (env.IsDevelopment()) 49 | { 50 | app.UseDeveloperExceptionPage(); 51 | } 52 | 53 | app.UseStaticFiles(); 54 | 55 | app.UseAuthentication(); 56 | 57 | app.UseMvc(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /IdentityServer/Web/Views/Auth/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Access Denied"; 3 | } 4 | 5 |
    6 |

    Access Denied

    7 |

    8 |

    You are not welcome!

    9 |

    10 |
    11 | 12 | -------------------------------------------------------------------------------- /IdentityServer/Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 
    2 |

    Hello @(User.Identity.IsAuthenticated ? User.Identity.Name : "World")

    3 | @if (User.Identity.IsAuthenticated) 4 | { 5 |
    6 | Log Out 7 |
    8 | } 9 | else 10 | { 11 |

    12 | Log In 13 |

    14 | } 15 |
    -------------------------------------------------------------------------------- /IdentityServer/Web/Views/Home/Spa.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Spa"; 3 | } 4 | 5 |
    6 |

    Spa

    7 | 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 57 | 58 | -------------------------------------------------------------------------------- /IdentityServer/Web/Views/Home/UserInformation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "User Information"; 3 | } 4 | 5 |
    6 |

    User Information

    7 |

    8 |

    9 | @User.Identity.Name 10 |
    11 |
    12 |
    13 | @foreach (var claim in User.Claims) 14 | { 15 |
    @claim.Issuer | @claim.Type | @claim.Value
    16 | } 17 |
    18 |

    19 | 20 |
    21 | Log Out 22 |
    23 |
    24 | 25 | -------------------------------------------------------------------------------- /IdentityServer/Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 38 | 39 | @RenderBody() 40 | 41 |
    42 | 43 | 44 | -------------------------------------------------------------------------------- /IdentityServer/Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /IdentityServer/Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /IdentityServer/Web/Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/callback.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/scripts/oidc-client-config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var config = { 3 | authority: "http://localhost:5000", 4 | client_id: "spa", 5 | popup_redirect_uri: "http://localhost:3003/callback.html", 6 | response_type: "id_token token", 7 | scope: "openid profile api1", 8 | post_logout_redirect_uri: "http://localhost:3003/signoutcallback.html", 9 | }; 10 | window.oidc = { userManager: new UserManager(config) }; 11 | })(); -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/signoutcallback.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/styles/css/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 20px; 3 | } 4 | 5 | .form, 6 | .validation-summary-errors { 7 | max-width: 330px; 8 | padding: 15px; 9 | margin: 0 auto; 10 | } 11 | 12 | .form .form-heading { 13 | margin-bottom: 10px; 14 | } 15 | 16 | .form .form-control { 17 | position: relative; 18 | height: auto; 19 | -webkit-box-sizing: border-box; 20 | -moz-box-sizing: border-box; 21 | box-sizing: border-box; 22 | padding: 10px; 23 | font-size: 16px; 24 | } 25 | 26 | .form .form-control:focus { 27 | z-index: 2; 28 | } 29 | 30 | .form input { 31 | border-radius: 0; 32 | margin-bottom: -1px; 33 | } 34 | 35 | .form .first { 36 | margin-bottom: -1px; 37 | border-top-right-radius: 5px; 38 | border-top-left-radius: 5px; 39 | } 40 | 41 | .form .last { 42 | margin-bottom: 10px; 43 | border-bottom-left-radius: 5px; 44 | border-bottom-right-radius: 5px; 45 | } 46 | 47 | .navbar-nav > li a { 48 | position: relative; 49 | display: block; 50 | padding: 15px; 51 | color: #777; 52 | } 53 | 54 | .navbar-default .navbar-nav > li a:hover, 55 | .navbar-nav > li > a:hover { 56 | text-decoration: none; 57 | color: #fff; 58 | background-color: #999; 59 | } 60 | 61 | .external-providers { 62 | max-width: 330px; 63 | padding: 15px; 64 | margin: auto; 65 | } 66 | 67 | .external-providers a { 68 | width: 100%; 69 | margin-bottom: 10px; 70 | } -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/IdentityServer/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Local/Web/Controllers/AuthController.cs: -------------------------------------------------------------------------------- 1 | using Web.Models; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Web.Services; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using System.Security.Claims; 7 | using System.Collections.Generic; 8 | using Microsoft.AspNetCore.Authentication.Cookies; 9 | 10 | namespace Web.Controllers 11 | { 12 | [Route("auth")] 13 | public class AuthController : Controller 14 | { 15 | private readonly IUserService userService; 16 | 17 | public AuthController(IUserService userService) 18 | { 19 | this.userService = userService; 20 | } 21 | 22 | [Route("login")] 23 | public IActionResult LogIn() 24 | { 25 | return View(new LogInModel()); 26 | } 27 | 28 | [Route("login")] 29 | [ValidateAntiForgeryToken] 30 | [HttpPost] 31 | public async Task LogIn(LogInModel model) 32 | { 33 | if (!ModelState.IsValid) 34 | { 35 | return View(model); 36 | } 37 | 38 | var user = await userService.Authenticate(model.Email, model.Password); 39 | if (user == null) 40 | { 41 | ModelState.AddModelError("InvalidCredentials", "Could not validate your credentials"); 42 | return View(model); 43 | } 44 | 45 | return await SignInUser(user); 46 | } 47 | 48 | [Route("register")] 49 | public IActionResult Register() 50 | { 51 | return View(); 52 | } 53 | 54 | [Route("register")] 55 | [ValidateAntiForgeryToken] 56 | [HttpPost] 57 | public async Task Register(RegisterModel model) 58 | { 59 | if (!ModelState.IsValid) 60 | { 61 | return View(model); 62 | } 63 | 64 | var user = await userService.Add(model.Name, model.Email, model.Password); 65 | 66 | return await SignInUser(user); 67 | } 68 | 69 | [Route("accessdenied")] 70 | public IActionResult AccessDenied() 71 | { 72 | return View(); 73 | } 74 | 75 | [Route("logout")] 76 | [ValidateAntiForgeryToken] 77 | [HttpPost] 78 | public async Task LogOut() 79 | { 80 | await HttpContext.SignOutAsync(); 81 | return RedirectToAction("Index", "Home"); 82 | } 83 | 84 | [Route("loginexternal/{id}")] 85 | public Task LogInExternal(string id) { 86 | //return HttpContext.ChallengeAsync(id, new AuthenticationProperties { RedirectUri = "/userinfo" }); 87 | return HttpContext.ChallengeAsync(id, new AuthenticationProperties { RedirectUri = "/auth/registerexternal" }); 88 | } 89 | 90 | [Route("registerexternal")] 91 | public async Task RegisterExternal(string authprovider) 92 | { 93 | var authResult = await HttpContext.AuthenticateAsync("TempCookie"); 94 | if (!authResult.Succeeded) 95 | { 96 | return RedirectToAction("Index", "Home"); 97 | } 98 | 99 | var user = await userService.AuthenticateExternal(authResult.Principal.FindFirstValue(ClaimTypes.NameIdentifier)); 100 | if (user != null) 101 | { 102 | return await SignInExternal(user); 103 | } 104 | 105 | return View(new RegisterExternalModel { 106 | Name = authResult.Principal.FindFirstValue(ClaimTypes.Name), 107 | Email = authResult.Principal.FindFirstValue(ClaimTypes.Email) 108 | }); 109 | } 110 | 111 | [Route("registerexternal/{id}")] 112 | [ValidateAntiForgeryToken] 113 | [HttpPost] 114 | public async Task RegisterExternal(string id, RegisterExternalModel model) 115 | { 116 | var authResult = await HttpContext.AuthenticateAsync("TempCookie"); 117 | if (!authResult.Succeeded) 118 | { 119 | return RedirectToAction("Index", "Home"); 120 | } 121 | 122 | if (!ModelState.IsValid) 123 | { 124 | return View(model); 125 | } 126 | 127 | var user = await userService.AddExternal(authResult.Principal.FindFirstValue(ClaimTypes.NameIdentifier), model.Name, model.Email); 128 | 129 | return await SignInExternal(user); 130 | } 131 | 132 | private async Task SignInExternal(User user) 133 | { 134 | await HttpContext.SignOutAsync("TempCookie"); 135 | return await SignInUser(user); 136 | } 137 | private async Task SignInUser(User user) 138 | { 139 | var claims = new List 140 | { 141 | new Claim(ClaimTypes.NameIdentifier, user.Email), 142 | new Claim(ClaimTypes.Name, user.Username), 143 | new Claim(ClaimTypes.Email, user.Email), 144 | }; 145 | var identity = new ClaimsIdentity(claims); 146 | var principal = new ClaimsPrincipal(identity); 147 | 148 | await HttpContext.SignInAsync(principal); 149 | 150 | return RedirectToAction("UserInformation", "Home"); 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /Local/Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace Web.Controllers 5 | { 6 | public class HomeController : Controller 7 | { 8 | [Route("")] 9 | public IActionResult Index() 10 | { 11 | return View(); 12 | } 13 | 14 | [Route("userinfo")] 15 | [Authorize] 16 | public IActionResult UserInformation() 17 | { 18 | return View(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Local/Web/Models/LogInModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Web.Models 4 | { 5 | public class LogInModel 6 | { 7 | [Required(ErrorMessage = "Have to supply an e-mail address")] 8 | public string Email { get; set; } 9 | 10 | [Required(ErrorMessage = "Have to supply a password")] 11 | public string Password { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Local/Web/Models/RegisterExternalModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Web.Models 4 | { 5 | public class RegisterExternalModel 6 | { 7 | [Required(ErrorMessage = "Have to supply a name")] 8 | public string Name { get; set; } 9 | 10 | [Required(ErrorMessage = "Have to supply an e-mail address")] 11 | public string Email { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Local/Web/Models/RegisterModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Web.Models 4 | { 5 | public class RegisterModel 6 | { 7 | [Required(ErrorMessage = "Have to supply a name")] 8 | public string Name { get; set; } 9 | 10 | [Required(ErrorMessage = "Have to supply an e-mail address")] 11 | public string Email { get; set; } 12 | 13 | [Required(ErrorMessage = "Have to supply a password")] 14 | public string Password { get; set; } 15 | 16 | [Compare("Password", ErrorMessage = "The repeat password did not seem correct")] 17 | public string RepeatPassword { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Local/Web/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace Web 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Local/Web/Services/DummyUserService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Web.Services 11 | { 12 | public class DummyUserService : IUserService 13 | { 14 | private IDictionary _users = new Dictionary(); 15 | private IDictionary _externalUsers = new Dictionary(); 16 | 17 | public DummyUserService() 18 | { 19 | InitializeStorage(); 20 | } 21 | 22 | public Task Add(string name, string email, string password) 23 | { 24 | if (_users.ContainsKey(name.ToLower())) 25 | { 26 | throw new InvalidOperationException("Username already in use"); 27 | } 28 | 29 | var user = new User(name, email); 30 | _users.Add(email.ToLower(), (User: user, Password: HashString(password))); 31 | PersistData(); 32 | return Task.FromResult(user); 33 | } 34 | public Task Authenticate(string name, string password) 35 | { 36 | if (_users.ContainsKey(name.ToLower())) 37 | { 38 | if (_users[name.ToLower()].Password == HashString(password)) 39 | { 40 | return Task.FromResult(_users[name.ToLower()].User); 41 | } 42 | } 43 | return null; 44 | } 45 | 46 | public Task AddExternal(string id, string name, string email) 47 | { 48 | if (_users.ContainsKey(id)) 49 | { 50 | throw new InvalidOperationException("Id already in use"); 51 | } 52 | 53 | var user = new User(name, email); 54 | _externalUsers.Add(id, user); 55 | PersistData(); 56 | return Task.FromResult(user); 57 | } 58 | public Task AuthenticateExternal(string id) 59 | { 60 | if (_externalUsers.ContainsKey(id)) 61 | { 62 | return Task.FromResult(_externalUsers[id]); 63 | } 64 | return Task.FromResult(null); 65 | } 66 | 67 | private void InitializeStorage() 68 | { 69 | if (File.Exists("storage.json")) 70 | { 71 | var str = File.ReadAllText("storage.json"); 72 | var data = JObject.Parse(str); 73 | _users = data.GetValue("users").ToObject>(); 74 | _externalUsers = data.GetValue("externalUsers").ToObject>(); 75 | } 76 | } 77 | private void PersistData() 78 | { 79 | var data = new { users = _users, externalUsers = _externalUsers }; 80 | var str = JsonConvert.SerializeObject(data); 81 | File.WriteAllText("storage.json", str); 82 | } 83 | private string HashString(string str) 84 | { 85 | var message = Encoding.Unicode.GetBytes(str); 86 | var hash = new SHA256Managed(); 87 | 88 | var hashValue = hash.ComputeHash(message); 89 | return Encoding.Unicode.GetString(hashValue); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Local/Web/Services/IUserService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Web.Services 4 | { 5 | public interface IUserService 6 | { 7 | Task Authenticate(string email, string password); 8 | Task Add(string name, string email, string password); 9 | 10 | Task AuthenticateExternal(string id); 11 | Task AddExternal(string id, string name, string email); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Local/Web/Services/User.cs: -------------------------------------------------------------------------------- 1 | namespace Web.Services 2 | { 3 | public class User 4 | { 5 | public User(string username, string email) 6 | { 7 | Username = username; 8 | Email = email; 9 | } 10 | 11 | public string Username { get; } 12 | public string Email { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Local/Web/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.Cookies; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Web.Services; 6 | 7 | namespace Web 8 | { 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | services.AddMvc(); 14 | services.AddSingleton(); 15 | 16 | services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 17 | .AddCookie(options => 18 | { 19 | options.LoginPath = "/auth/login"; 20 | options.AccessDeniedPath = "/auth/accessdenied"; 21 | }) 22 | .AddFacebook(options => 23 | { 24 | options.AppId = "### INSERT FACEBOOK APP ID HERE ###"; 25 | options.AppSecret = "### INSERT FACEBOOK APP SECRET HERE ###"; 26 | 27 | options.SignInScheme = "TempCookie"; 28 | }) 29 | .AddTwitter(options => 30 | { 31 | options.ConsumerKey = "### INSERT TWITTER COMSUMER KEY HERE ###"; 32 | options.ConsumerSecret = "### INSERT TWITTER COMSUMER SECRET HERE ###"; 33 | 34 | options.SignInScheme = "TempCookie"; 35 | }) 36 | .AddCookie("TempCookie"); 37 | } 38 | 39 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 40 | { 41 | if (env.IsDevelopment()) 42 | { 43 | app.UseDeveloperExceptionPage(); 44 | } 45 | 46 | app.UseStaticFiles(); 47 | 48 | app.UseAuthentication(); 49 | 50 | app.UseMvc(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Local/Web/Views/Auth/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Access Denied"; 3 | } 4 | 5 |
    6 |

    Access Denied

    7 |

    8 |

    You are not welcome!

    9 |

    10 |
    11 | 12 | -------------------------------------------------------------------------------- /Local/Web/Views/Auth/LogIn.cshtml: -------------------------------------------------------------------------------- 1 | @inject Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider 2 | 3 | @model Web.Models.LogInModel 4 | @{ 5 | ViewData["Title"] = "Login"; 6 | } 7 | 8 | @if (!ViewData.ModelState.IsValid) 9 | { 10 |
    11 | } 12 | 13 |
    14 |

    Please sign in

    15 | 16 | 17 | 18 | 19 | 20 |
    21 | 22 |
    23 | 24 |
    25 | @foreach (var scheme in await schemeProvider.GetRequestHandlerSchemesAsync()) 26 | { 27 | @scheme.DisplayName 28 | } 29 |
    -------------------------------------------------------------------------------- /Local/Web/Views/Auth/Register.cshtml: -------------------------------------------------------------------------------- 1 | @inject Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider 2 | 3 | @model Web.Models.RegisterModel 4 | @{ 5 | ViewData["Title"] = "Sign Up"; 6 | } 7 | 8 | @if (!ViewData.ModelState.IsValid) 9 | { 10 |
    11 | } 12 | 13 |
    14 |

    Please register

    15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    25 | 26 |
    27 | 28 |
    29 | @foreach (var scheme in await schemeProvider.GetRequestHandlerSchemesAsync()) 30 | { 31 | @scheme.DisplayName 32 | } 33 |
    34 | 35 | -------------------------------------------------------------------------------- /Local/Web/Views/Auth/RegisterExternal.cshtml: -------------------------------------------------------------------------------- 1 | @model Web.Models.RegisterExternalModel 2 | @{ 3 | ViewData["Title"] = "Sign Up"; 4 | } 5 | 6 | @if (!ViewData.ModelState.IsValid) 7 | { 8 |
    9 | } 10 | 11 |
    12 |

    Please register

    13 | 14 | 15 | 16 | 17 | 18 |
    19 | 20 | -------------------------------------------------------------------------------- /Local/Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 
    2 |

    Hello @(User.Identity.IsAuthenticated ? User.Identity.Name : "World")

    3 | @if (User.Identity.IsAuthenticated) 4 | { 5 |
    6 | Log Out 7 |
    8 | } 9 | else 10 | { 11 |

    12 | Register 13 | Log In 14 |

    15 | } 16 |
    -------------------------------------------------------------------------------- /Local/Web/Views/Home/UserInformation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "User Information"; 3 | } 4 | 5 |
    6 |

    User Information

    7 |

    8 |

    9 | @User.Identity.Name 10 |
    11 |
    12 |
    13 | @foreach (var claim in User.Claims) 14 | { 15 |
    @claim.Issuer | @claim.Type | @claim.Value
    16 | } 17 |
    18 |

    19 | 20 |
    21 | Log Out 22 |
    23 |
    24 | 25 | -------------------------------------------------------------------------------- /Local/Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 38 | 39 | @RenderBody() 40 | 41 |
    42 | 43 | 44 | -------------------------------------------------------------------------------- /Local/Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /Local/Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Local/Web/Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Local/Web/wwwroot/styles/css/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 20px; 3 | } 4 | 5 | .form, 6 | .validation-summary-errors { 7 | max-width: 330px; 8 | padding: 15px; 9 | margin: 0 auto; 10 | } 11 | 12 | .form .form-heading { 13 | margin-bottom: 10px; 14 | } 15 | 16 | .form .form-control { 17 | position: relative; 18 | height: auto; 19 | -webkit-box-sizing: border-box; 20 | -moz-box-sizing: border-box; 21 | box-sizing: border-box; 22 | padding: 10px; 23 | font-size: 16px; 24 | } 25 | 26 | .form .form-control:focus { 27 | z-index: 2; 28 | } 29 | 30 | .form input { 31 | border-radius: 0; 32 | margin-bottom: -1px; 33 | } 34 | 35 | .form .first { 36 | margin-bottom: -1px; 37 | border-top-right-radius: 5px; 38 | border-top-left-radius: 5px; 39 | } 40 | 41 | .form .last { 42 | margin-bottom: 10px; 43 | border-bottom-left-radius: 5px; 44 | border-bottom-right-radius: 5px; 45 | } 46 | 47 | .navbar-nav > li a { 48 | position: relative; 49 | display: block; 50 | padding: 15px; 51 | color: #777; 52 | } 53 | 54 | .navbar-default .navbar-nav > li a:hover, 55 | .navbar-nav > li > a:hover { 56 | text-decoration: none; 57 | color: #fff; 58 | background-color: #999; 59 | } 60 | 61 | .external-providers { 62 | max-width: 330px; 63 | padding: 15px; 64 | margin: auto; 65 | } 66 | 67 | .external-providers a { 68 | width: 100%; 69 | margin-bottom: 10px; 70 | } -------------------------------------------------------------------------------- /Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisKlug/asp.net-core-2.0-auth-demo/bff7fd38b24bc6990e62a57b19605310411ea8ae/Local/Web/wwwroot/styles/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Demo code for The whirlwind tour of Authentication and Authorization with ASP.NET Core 2 | 3 | To test the Local login example, open Startup.cs and add your Facebook and Twitter configuration values 4 | 5 | To test the Azure AD B2C example, open Startup.cs and add your Client ID, and add the required profiles (B2C_1_sign_in_or_up, B2C_1_sign_in, B2C_1_sign_up) in the Azure AD B2C --------------------------------------------------------------------------------