├── .gitignore ├── .nuget └── Authfix.Blazor.Extensions.Oidc.nuspec ├── Blazor.Oidc.sln ├── LICENSE ├── README.md ├── appveyor.yml ├── src ├── Blazor.Extensions.Oidc.JS │ ├── Blazor.Extensions.Oidc.JS.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── package.json │ ├── src │ │ ├── BlazorTypes.ts │ │ ├── BlazorUserManager.ts │ │ ├── GlobalExports.ts │ │ └── Initialize.ts │ ├── tsconfig.json │ └── webpack.config.js └── Blazor.Extensions.Oidc │ ├── Blazor.Extensions.Oidc.csproj │ ├── IdentityConfiguration.cs │ ├── IdentityUser.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── UserManager.cs └── test ├── Blazor.Extensions.Oidc.Test.Client ├── App.cshtml ├── Blazor.Extensions.Oidc.Test.Client.csproj ├── Pages │ ├── Callback.cshtml │ ├── Counter.cshtml │ ├── FetchData.cshtml │ ├── Index.cshtml │ └── _ViewImports.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── MainLayout.cshtml │ ├── NavMenu.cshtml │ └── SurveyPrompt.cshtml ├── _ViewImports.cshtml └── wwwroot │ ├── css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ │ ├── css │ │ │ └── open-iconic-bootstrap.min.css │ │ │ └── fonts │ │ │ ├── open-iconic.eot │ │ │ ├── open-iconic.otf │ │ │ ├── open-iconic.svg │ │ │ ├── open-iconic.ttf │ │ │ └── open-iconic.woff │ └── site.css │ └── index.html ├── Blazor.Extensions.Oidc.Test.Identity ├── Blazor.Extensions.Oidc.Test.Identity.csproj ├── Configuration.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Quickstart │ ├── Account │ │ ├── AccountController.cs │ │ ├── AccountOptions.cs │ │ ├── ExternalController.cs │ │ ├── ExternalProvider.cs │ │ ├── LoggedOutViewModel.cs │ │ ├── LoginInputModel.cs │ │ ├── LoginViewModel.cs │ │ ├── LogoutInputModel.cs │ │ ├── LogoutViewModel.cs │ │ └── RedirectViewModel.cs │ ├── Consent │ │ ├── ConsentController.cs │ │ ├── ConsentInputModel.cs │ │ ├── ConsentOptions.cs │ │ ├── ConsentViewModel.cs │ │ ├── ProcessConsentResult.cs │ │ └── ScopeViewModel.cs │ ├── Diagnostics │ │ ├── DiagnosticsController.cs │ │ └── DiagnosticsViewModel.cs │ ├── Extensions.cs │ ├── Grants │ │ ├── GrantsController.cs │ │ └── GrantsViewModel.cs │ ├── Home │ │ ├── ErrorViewModel.cs │ │ └── HomeController.cs │ ├── SecurityHeadersAttribute.cs │ └── TestUsers.cs ├── Startup.cs ├── Views │ ├── Account │ │ ├── LoggedOut.cshtml │ │ ├── Login.cshtml │ │ └── Logout.cshtml │ ├── Consent │ │ ├── Index.cshtml │ │ └── _ScopeListItem.cshtml │ ├── Diagnostics │ │ └── Index.cshtml │ ├── Grants │ │ └── Index.cshtml │ ├── Home │ │ └── Index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── Redirect.cshtml │ │ ├── _Layout.cshtml │ │ └── _ValidationSummary.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml └── wwwroot │ ├── css │ ├── site.css │ ├── site.less │ └── site.min.css │ ├── favicon.ico │ ├── icon.jpg │ ├── icon.png │ ├── js │ ├── signin-redirect.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 ├── Blazor.Extensions.Oidc.Test.Server ├── Blazor.Extensions.Oidc.Test.Server.csproj ├── Controllers │ └── SampleDataController.cs ├── Program.cs ├── Properties │ └── launchSettings.json └── Startup.cs ├── Blazor.Extensions.Oidc.Test.Shared ├── Blazor.Extensions.Oidc.Test.Shared.csproj └── WeatherForecast.cs └── global.json /.gitignore: -------------------------------------------------------------------------------- 1 | tempkey.rsa 2 | authfix.blazor.extensions.oidc.js 3 | 4 | # Created by https://www.gitignore.io/api/csharp,visualstudio,visualstudiocode 5 | 6 | ### Csharp ### 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | ## 10 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 11 | 12 | # User-specific files 13 | *.suo 14 | *.user 15 | *.userosscache 16 | *.sln.docstates 17 | 18 | # User-specific files (MonoDevelop/Xamarin Studio) 19 | *.userprefs 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUNIT 46 | *.VisualState.xml 47 | TestResult.xml 48 | 49 | # Build Results of an ATL Project 50 | [Dd]ebugPS/ 51 | [Rr]eleasePS/ 52 | dlldata.c 53 | 54 | # Benchmark Results 55 | BenchmarkDotNet.Artifacts/ 56 | 57 | # .NET Core 58 | project.lock.json 59 | project.fragment.lock.json 60 | artifacts/ 61 | 62 | # StyleCop 63 | StyleCopReport.xml 64 | 65 | # Files built by Visual Studio 66 | *_i.c 67 | *_p.c 68 | *_i.h 69 | *.ilk 70 | *.meta 71 | *.obj 72 | *.iobj 73 | *.pch 74 | *.pdb 75 | *.ipdb 76 | *.pgc 77 | *.pgd 78 | *.rsp 79 | *.sbr 80 | *.tlb 81 | *.tli 82 | *.tlh 83 | *.tmp 84 | *.tmp_proj 85 | *.log 86 | *.vspscc 87 | *.vssscc 88 | .builds 89 | *.pidb 90 | *.svclog 91 | *.scc 92 | 93 | # Chutzpah Test files 94 | _Chutzpah* 95 | 96 | # Visual C++ cache files 97 | ipch/ 98 | *.aps 99 | *.ncb 100 | *.opendb 101 | *.opensdf 102 | *.sdf 103 | *.cachefile 104 | *.VC.db 105 | *.VC.VC.opendb 106 | 107 | # Visual Studio profiler 108 | *.psess 109 | *.vsp 110 | *.vspx 111 | *.sap 112 | 113 | # Visual Studio Trace Files 114 | *.e2e 115 | 116 | # TFS 2012 Local Workspace 117 | $tf/ 118 | 119 | # Guidance Automation Toolkit 120 | *.gpState 121 | 122 | # ReSharper is a .NET coding add-in 123 | _ReSharper*/ 124 | *.[Rr]e[Ss]harper 125 | *.DotSettings.user 126 | 127 | # JustCode is a .NET coding add-in 128 | .JustCode 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # The packages folder can be ignored because of Package Restore 188 | **/[Pp]ackages/* 189 | # except build/, which is used as an MSBuild target. 190 | !**/[Pp]ackages/build/ 191 | # Uncomment if necessary however generally it will be regenerated when needed 192 | #!**/[Pp]ackages/repositories.config 193 | # NuGet v3's project.json files produces more ignorable files 194 | *.nuget.props 195 | *.nuget.targets 196 | 197 | # Microsoft Azure Build Output 198 | csx/ 199 | *.build.csdef 200 | 201 | # Microsoft Azure Emulator 202 | ecf/ 203 | rcf/ 204 | 205 | # Windows Store app package directories and files 206 | AppPackages/ 207 | BundleArtifacts/ 208 | Package.StoreAssociation.xml 209 | _pkginfo.txt 210 | *.appx 211 | 212 | # Visual Studio cache files 213 | # files ending in .cache can be ignored 214 | *.[Cc]ache 215 | # but keep track of directories ending in .cache 216 | !*.[Cc]ache/ 217 | 218 | # Others 219 | ClientBin/ 220 | ~$* 221 | *~ 222 | *.dbmdl 223 | *.dbproj.schemaview 224 | *.jfm 225 | *.pfx 226 | *.publishsettings 227 | orleans.codegen.cs 228 | 229 | # Including strong name files can present a security risk 230 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 231 | #*.snk 232 | 233 | # Since there are multiple workflows, uncomment next line to ignore bower_components 234 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 235 | #bower_components/ 236 | 237 | # RIA/Silverlight projects 238 | Generated_Code/ 239 | 240 | # Backup & report files from converting an old project file 241 | # to a newer Visual Studio version. Backup files are not needed, 242 | # because we have git ;-) 243 | _UpgradeReport_Files/ 244 | Backup*/ 245 | UpgradeLog*.XML 246 | UpgradeLog*.htm 247 | ServiceFabricBackup/ 248 | *.rptproj.bak 249 | 250 | # SQL Server files 251 | *.mdf 252 | *.ldf 253 | *.ndf 254 | 255 | # Business Intelligence projects 256 | *.rdl.data 257 | *.bim.layout 258 | *.bim_*.settings 259 | *.rptproj.rsuser 260 | 261 | # Microsoft Fakes 262 | FakesAssemblies/ 263 | 264 | # GhostDoc plugin setting file 265 | *.GhostDoc.xml 266 | 267 | # Node.js Tools for Visual Studio 268 | .ntvs_analysis.dat 269 | node_modules/ 270 | 271 | # Visual Studio 6 build log 272 | *.plg 273 | 274 | # Visual Studio 6 workspace options file 275 | *.opt 276 | 277 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 278 | *.vbw 279 | 280 | # Visual Studio LightSwitch build output 281 | **/*.HTMLClient/GeneratedArtifacts 282 | **/*.DesktopClient/GeneratedArtifacts 283 | **/*.DesktopClient/ModelManifest.xml 284 | **/*.Server/GeneratedArtifacts 285 | **/*.Server/ModelManifest.xml 286 | _Pvt_Extensions 287 | 288 | # Paket dependency manager 289 | .paket/paket.exe 290 | paket-files/ 291 | 292 | # FAKE - F# Make 293 | .fake/ 294 | 295 | # JetBrains Rider 296 | .idea/ 297 | *.sln.iml 298 | 299 | # CodeRush 300 | .cr/ 301 | 302 | # Python Tools for Visual Studio (PTVS) 303 | __pycache__/ 304 | *.pyc 305 | 306 | # Cake - Uncomment if you are using it 307 | # tools/** 308 | # !tools/packages.config 309 | 310 | # Tabs Studio 311 | *.tss 312 | 313 | # Telerik's JustMock configuration file 314 | *.jmconfig 315 | 316 | # BizTalk build output 317 | *.btp.cs 318 | *.btm.cs 319 | *.odx.cs 320 | *.xsd.cs 321 | 322 | # OpenCover UI analysis results 323 | OpenCover/ 324 | 325 | # Azure Stream Analytics local run output 326 | ASALocalRun/ 327 | 328 | # MSBuild Binary and Structured Log 329 | *.binlog 330 | 331 | # NVidia Nsight GPU debugger configuration file 332 | *.nvuser 333 | 334 | # MFractors (Xamarin productivity tool) working folder 335 | .mfractor/ 336 | 337 | ### VisualStudioCode ### 338 | .vscode/* 339 | !.vscode/settings.json 340 | !.vscode/tasks.json 341 | !.vscode/launch.json 342 | !.vscode/extensions.json 343 | 344 | ### VisualStudio ### 345 | ## Ignore Visual Studio temporary files, build results, and 346 | ## files generated by popular Visual Studio add-ons. 347 | ## 348 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 349 | 350 | # User-specific files 351 | 352 | # User-specific files (MonoDevelop/Xamarin Studio) 353 | 354 | # Build results 355 | 356 | # Visual Studio 2015/2017 cache/options directory 357 | # Uncomment if you have tasks that create the project's static files in wwwroot 358 | #wwwroot/ 359 | 360 | # Visual Studio 2017 auto generated files 361 | 362 | # MSTest test Results 363 | 364 | # NUNIT 365 | 366 | # Build Results of an ATL Project 367 | 368 | # Benchmark Results 369 | 370 | # .NET Core 371 | 372 | # StyleCop 373 | 374 | # Files built by Visual Studio 375 | 376 | # Chutzpah Test files 377 | 378 | # Visual C++ cache files 379 | 380 | # Visual Studio profiler 381 | 382 | # Visual Studio Trace Files 383 | 384 | # TFS 2012 Local Workspace 385 | 386 | # Guidance Automation Toolkit 387 | 388 | # ReSharper is a .NET coding add-in 389 | 390 | # JustCode is a .NET coding add-in 391 | 392 | # TeamCity is a build add-in 393 | 394 | # DotCover is a Code Coverage Tool 395 | 396 | # AxoCover is a Code Coverage Tool 397 | 398 | # Visual Studio code coverage results 399 | 400 | # NCrunch 401 | 402 | # MightyMoose 403 | 404 | # Web workbench (sass) 405 | 406 | # Installshield output folder 407 | 408 | # DocProject is a documentation generator add-in 409 | 410 | # Click-Once directory 411 | 412 | # Publish Web Output 413 | # Note: Comment the next line if you want to checkin your web deploy settings, 414 | # but database connection strings (with potential passwords) will be unencrypted 415 | 416 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 417 | # checkin your Azure Web App publish settings, but sensitive information contained 418 | # in these scripts will be unencrypted 419 | 420 | # NuGet Packages 421 | # The packages folder can be ignored because of Package Restore 422 | # except build/, which is used as an MSBuild target. 423 | # Uncomment if necessary however generally it will be regenerated when needed 424 | #!**/[Pp]ackages/repositories.config 425 | # NuGet v3's project.json files produces more ignorable files 426 | 427 | # Microsoft Azure Build Output 428 | 429 | # Microsoft Azure Emulator 430 | 431 | # Windows Store app package directories and files 432 | 433 | # Visual Studio cache files 434 | # files ending in .cache can be ignored 435 | # but keep track of directories ending in .cache 436 | 437 | # Others 438 | 439 | # Including strong name files can present a security risk 440 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 441 | #*.snk 442 | 443 | # Since there are multiple workflows, uncomment next line to ignore bower_components 444 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 445 | #bower_components/ 446 | 447 | # RIA/Silverlight projects 448 | 449 | # Backup & report files from converting an old project file 450 | # to a newer Visual Studio version. Backup files are not needed, 451 | # because we have git ;-) 452 | 453 | # SQL Server files 454 | 455 | # Business Intelligence projects 456 | 457 | # Microsoft Fakes 458 | 459 | # GhostDoc plugin setting file 460 | 461 | # Node.js Tools for Visual Studio 462 | 463 | # Visual Studio 6 build log 464 | 465 | # Visual Studio 6 workspace options file 466 | 467 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 468 | 469 | # Visual Studio LightSwitch build output 470 | 471 | # Paket dependency manager 472 | 473 | # FAKE - F# Make 474 | 475 | # JetBrains Rider 476 | 477 | # CodeRush 478 | 479 | # Python Tools for Visual Studio (PTVS) 480 | 481 | # Cake - Uncomment if you are using it 482 | # tools/** 483 | # !tools/packages.config 484 | 485 | # Tabs Studio 486 | 487 | # Telerik's JustMock configuration file 488 | 489 | # BizTalk build output 490 | 491 | # OpenCover UI analysis results 492 | 493 | # Azure Stream Analytics local run output 494 | 495 | # MSBuild Binary and Structured Log 496 | 497 | # NVidia Nsight GPU debugger configuration file 498 | 499 | # MFractors (Xamarin productivity tool) working folder 500 | 501 | 502 | # End of https://www.gitignore.io/api/csharp,visualstudio,visualstudiocode -------------------------------------------------------------------------------- /.nuget/Authfix.Blazor.Extensions.Oidc.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Authfix.Blazor.Extensions.Oidc 5 | 1.0.0 6 | 7 | Authfix 8 | 9 | true 10 | https://github.com/Authfix/Blazor-Oidc/blob/develop/LICENSE 11 | https://github.com/Authfix/Blazor-Oidc 12 | The package aims to add the possibility to use oidc-client javascript library inside a Blazor project by using Blazor's interop capabilities. 13 | Add oidc-client library support for Blazor 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Blazor.Oidc.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Oidc.JS", "src\Blazor.Extensions.Oidc.JS\Blazor.Extensions.Oidc.JS.csproj", "{034FB1F5-7928-4B1C-9106-F6139F4B0446}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Oidc", "src\Blazor.Extensions.Oidc\Blazor.Extensions.Oidc.csproj", "{F61B04E1-DB22-42C8-92FA-DDE1C25E2E1B}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{FBED13AE-7A93-4B89-91FC-7F78E8ECADB3}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{DB3DFB2D-9364-491E-B213-5255A63800B7}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Oidc.Test.Server", "test\Blazor.Extensions.Oidc.Test.Server\Blazor.Extensions.Oidc.Test.Server.csproj", "{08FA79FA-6F4B-4029-88C5-1655CE61F2D5}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Oidc.Test.Client", "test\Blazor.Extensions.Oidc.Test.Client\Blazor.Extensions.Oidc.Test.Client.csproj", "{F2ED9BFA-8E0C-454D-A70F-BC87EF6544E1}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Oidc.Test.Shared", "test\Blazor.Extensions.Oidc.Test.Shared\Blazor.Extensions.Oidc.Test.Shared.csproj", "{D0E1A332-E04C-40D9-8CA9-B8E20648AE84}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Extensions.Oidc.Test.Identity", "test\Blazor.Extensions.Oidc.Test.Identity\Blazor.Extensions.Oidc.Test.Identity.csproj", "{B321F8EB-78F9-427D-BEF1-9C59F55A6006}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {034FB1F5-7928-4B1C-9106-F6139F4B0446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {034FB1F5-7928-4B1C-9106-F6139F4B0446}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {034FB1F5-7928-4B1C-9106-F6139F4B0446}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {034FB1F5-7928-4B1C-9106-F6139F4B0446}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {F61B04E1-DB22-42C8-92FA-DDE1C25E2E1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {F61B04E1-DB22-42C8-92FA-DDE1C25E2E1B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {F61B04E1-DB22-42C8-92FA-DDE1C25E2E1B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {F61B04E1-DB22-42C8-92FA-DDE1C25E2E1B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {08FA79FA-6F4B-4029-88C5-1655CE61F2D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {08FA79FA-6F4B-4029-88C5-1655CE61F2D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {08FA79FA-6F4B-4029-88C5-1655CE61F2D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {08FA79FA-6F4B-4029-88C5-1655CE61F2D5}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {F2ED9BFA-8E0C-454D-A70F-BC87EF6544E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {F2ED9BFA-8E0C-454D-A70F-BC87EF6544E1}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {F2ED9BFA-8E0C-454D-A70F-BC87EF6544E1}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {F2ED9BFA-8E0C-454D-A70F-BC87EF6544E1}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {D0E1A332-E04C-40D9-8CA9-B8E20648AE84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {D0E1A332-E04C-40D9-8CA9-B8E20648AE84}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {D0E1A332-E04C-40D9-8CA9-B8E20648AE84}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {D0E1A332-E04C-40D9-8CA9-B8E20648AE84}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {B321F8EB-78F9-427D-BEF1-9C59F55A6006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {B321F8EB-78F9-427D-BEF1-9C59F55A6006}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {B321F8EB-78F9-427D-BEF1-9C59F55A6006}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {B321F8EB-78F9-427D-BEF1-9C59F55A6006}.Release|Any CPU.Build.0 = Release|Any CPU 52 | EndGlobalSection 53 | GlobalSection(SolutionProperties) = preSolution 54 | HideSolutionNode = FALSE 55 | EndGlobalSection 56 | GlobalSection(NestedProjects) = preSolution 57 | {034FB1F5-7928-4B1C-9106-F6139F4B0446} = {FBED13AE-7A93-4B89-91FC-7F78E8ECADB3} 58 | {F61B04E1-DB22-42C8-92FA-DDE1C25E2E1B} = {FBED13AE-7A93-4B89-91FC-7F78E8ECADB3} 59 | {08FA79FA-6F4B-4029-88C5-1655CE61F2D5} = {DB3DFB2D-9364-491E-B213-5255A63800B7} 60 | {F2ED9BFA-8E0C-454D-A70F-BC87EF6544E1} = {DB3DFB2D-9364-491E-B213-5255A63800B7} 61 | {D0E1A332-E04C-40D9-8CA9-B8E20648AE84} = {DB3DFB2D-9364-491E-B213-5255A63800B7} 62 | {B321F8EB-78F9-427D-BEF1-9C59F55A6006} = {DB3DFB2D-9364-491E-B213-5255A63800B7} 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {7E45CDD9-9817-451C-8431-5E420BFD2AC3} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Bailly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oidc Blazor Extensions Oidc 2 | 3 | This package adds a [Oidc-client](https://github.com/IdentityModel/oidc-client-js) library for [Microsoft ASP.NET Blazor](https://github.com/aspnet/Blazor). 4 | 5 | The package aims to add the possibility to use oidc-client javascript library inside a Blazor project by using Blazor's interop capabilities. 6 | 7 | # Features 8 | 9 | This package *does not* implements all public features of the oidc-client library. 10 | 11 | > Note: Only basic authentication is available for now. 12 | 13 | # Sample usage 14 | 15 | The following snippet shows how to setup the oidc client and allow authentication. 16 | 17 | On the page where we want to begin the authentication process 18 | 19 | ```c# 20 | var config = new IdentityConfiguration 21 | { 22 | Authority = "http://localhost:50000", 23 | ClientId = "js", 24 | PostLogoutRedirectUri = "http://localhost:50001/index.html", 25 | RedirectUri = "http://localhost:50001/callback", 26 | ResponseType = "id_token token", 27 | Scope = "openid profile api1" 28 | }; 29 | 30 | var manager = new UserManager(config); 31 | var user = await manager.GetUser(); 32 | 33 | if(user == null) 34 | { 35 | await manager.SignIn(); 36 | } 37 | else 38 | { 39 | // do anything 40 | } 41 | ``` 42 | 43 | And on the callback page 44 | 45 | ```c# 46 | var manager = new UserManager(config); 47 | await manager.SignInRedirectCallback(); 48 | ``` 49 | 50 | # Contributions and feedback 51 | 52 | Please feel free to use the component, open issues, fix bugs or provide feedback. 53 | 54 | # Contributors 55 | 56 | The following people are the maintainers of the project: 57 | 58 | - [Thomas Bailly](https://github.com/authfix) -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - choco install gitversion.portable -pre -y 3 | 4 | assembly_info: 5 | patch: false 6 | 7 | before_build: 8 | - dotnet restore 9 | - ps: gitversion /l console /output buildserver /updateAssemblyInfo 10 | 11 | build_script: 12 | - ps: dotnet build src/Blazor.Extensions.Oidc.JS/Blazor.Extensions.Oidc.JS.csproj --configuration release --no-restore 13 | - ps: dotnet build src/Blazor.Extensions.Oidc/Blazor.Extensions.Oidc.csproj --configuration release --no-restore 14 | 15 | after_build: 16 | - cmd: nuget pack .nuget/Authfix.Blazor.Extensions.Oidc.nuspec -version "%GitVersion_NuGetVersion%" -prop "target=%CONFIGURATION%" 17 | 18 | artifacts: 19 | - path: '*.nupkg' 20 | name: Authfix.Blazor.Extensions.Oidc 21 | 22 | branches: 23 | only: 24 | - master -------------------------------------------------------------------------------- /src/Blazor.Extensions.Oidc.JS/Blazor.Extensions.Oidc.JS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Library 6 | false 7 | false 8 | false 9 | latest 10 | true 11 | Latest 12 | ${DefaultItemExcludes};dist\**;node_modules\** 13 | 14 | 15 | true 16 | Authfix.Blazor.Extensions.Oidc.JS 17 | Authfix.Blazor.Extensions.Oidc.JS 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Blazor.Extensions.Oidc.JS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Authfix.Blazor.Extensions.Oidc.JS")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Oidc extension for Blazor applications")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("5f15ee16-bcbf-4ae0-8f98-5fef0ee4d670")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | [assembly: AssemblyVersion("1.0.0.0")] 32 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /src/Blazor.Extensions.Oidc.JS/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "name": "authfix.blazor.extensions.oidc", 4 | "private": true, 5 | "scripts": { 6 | "build": "webpack", 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "oidc-client": "1.5.2" 11 | }, 12 | "devDependencies": { 13 | "@types/emscripten": "0.0.31", 14 | "typescript": "^2.6.1", 15 | "webpack": "^3.8.1", 16 | "ts-loader": "^3.2.0" 17 | } 18 | } -------------------------------------------------------------------------------- /src/Blazor.Extensions.Oidc.JS/src/BlazorTypes.ts: -------------------------------------------------------------------------------- 1 | // Declare types here until we've Blazor.d.ts 2 | export interface System_Object { System_Object__DO_NOT_IMPLEMENT: any }; 3 | export interface System_String extends System_Object { System_String__DO_NOT_IMPLEMENT: any } 4 | 5 | export interface Platform { 6 | toJavaScriptString(dotNetString: System_String): string; 7 | } 8 | 9 | export type BlazorType = { 10 | registerFunction(identifier: string, implementation: Function), 11 | platform: Platform 12 | }; -------------------------------------------------------------------------------- /src/Blazor.Extensions.Oidc.JS/src/BlazorUserManager.ts: -------------------------------------------------------------------------------- 1 | import { BlazorType } from "./BlazorTypes"; 2 | import * as Oidc from "oidc-client"; 3 | import { UserManagerSettings } from "oidc-client"; 4 | 5 | export class BlazorUserManager { 6 | 7 | public static initialize() { 8 | const Blazor: BlazorType = window["Blazor"]; 9 | 10 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.Init', (configuration: any) => { 11 | 12 | var localConfiguration: UserManagerSettings = 13 | { 14 | authority: configuration.authority, 15 | client_id: configuration.clientId, 16 | redirect_uri: configuration.redirectUri, 17 | response_type: configuration.responseType, 18 | scope: configuration.scope, 19 | post_logout_redirect_uri: configuration.postLogoutRedirectUri 20 | } 21 | 22 | window["BlazorExtensions"].UserManager = new Oidc.UserManager(localConfiguration); 23 | }); 24 | 25 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.ClearStaleState', () => { 26 | return window["BlazorExtensions"].UserManager.clearStaleState(); 27 | }); 28 | 29 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.GetUser', () => { 30 | return window["BlazorExtensions"].UserManager.getUser(); 31 | }); 32 | 33 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.StoreUser', (userToStore: Oidc.User) => { 34 | return window["BlazorExtensions"].UserManager.storeUser(userToStore); 35 | }); 36 | 37 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.RemoveUser', () => { 38 | return window["BlazorExtensions"].UserManager.removeUser(); 39 | }); 40 | 41 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignInPopup', (args?: any) => { 42 | return window["BlazorExtensions"].UserManager.signinPopup(args); 43 | }); 44 | 45 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignInPopupCallback', (url?: string) => { 46 | return window["BlazorExtensions"].UserManager.signinPopupCallback(url); 47 | }); 48 | 49 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignInSilent', (args?: any) => { 50 | return window["BlazorExtensions"].UserManager.signinSilent(args); 51 | }); 52 | 53 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignInSilentCallback', (url?: string) => { 54 | return window["BlazorExtensions"].UserManager.signinSilentCallback(url); 55 | }); 56 | 57 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignInRedirect', (args?: any) => { 58 | return window["BlazorExtensions"].UserManager.signinRedirect(args); 59 | }); 60 | 61 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignInRedirectCallback', (url?: string) => { 62 | console.log("Url : " + url); 63 | return window["BlazorExtensions"].UserManager.signinRedirectCallback(url); 64 | }); 65 | 66 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignOutPopup', (args?: any) => { 67 | return window["BlazorExtensions"].UserManager.signoutPopup(args); 68 | }); 69 | 70 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.SignOutPopupCallback', (url?: string, keepOpen?: boolean) => { 71 | return window["BlazorExtensions"].UserManager.signoutPopupCallback(url, keepOpen); 72 | }); 73 | 74 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.QuerySessionStatus', (args?: any) => { 75 | return window["BlazorExtensions"].UserManager.querySessionStatus(args); 76 | }); 77 | 78 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.RevokeAccessToken', () => { 79 | return window["BlazorExtensions"].UserManager.revokeAccessToken(); 80 | }); 81 | 82 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.StartSilentRenew', () => { 83 | return window["BlazorExtensions"].UserManager.startSilentRenew(); 84 | }); 85 | 86 | Blazor.registerFunction('Authfix.Blazor.Extensions.Oidc.StopSilentRenew', () => { 87 | return window["BlazorExtensions"].UserManager.stopSilentRenew(); 88 | }); 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /src/Blazor.Extensions.Oidc.JS/src/GlobalExports.ts: -------------------------------------------------------------------------------- 1 | if (typeof window !== 'undefined' && !window['BlazorExtensions']) { 2 | // When the library is loaded in a browser via a 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Blazor.Extensions.Oidc.Test.Identity.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Configuration.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer4; 2 | using IdentityServer4.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Blazor.Extensions.Oidc.Test.Identity 9 | { 10 | public class Configuration 11 | { 12 | public static IEnumerable GetApiResources() 13 | { 14 | return new List 15 | { 16 | new ApiResource("api1", "My API") 17 | }; 18 | } 19 | 20 | public static IEnumerable GetIdentityResources() 21 | { 22 | return new List 23 | { 24 | new IdentityResources.OpenId(), 25 | new IdentityResources.Profile(), 26 | }; 27 | } 28 | 29 | public static IEnumerable GetClients() 30 | { 31 | return new List 32 | { 33 | new Client 34 | { 35 | ClientId = "client", 36 | 37 | // no interactive user, use the clientid/secret for authentication 38 | AllowedGrantTypes = GrantTypes.ClientCredentials, 39 | 40 | // secret for authentication 41 | ClientSecrets = 42 | { 43 | new Secret("secret".Sha256()) 44 | }, 45 | 46 | // scopes that client has access to 47 | AllowedScopes = { "api1" }, 48 | AllowedCorsOrigins = 49 | { 50 | "http://localhost:50001" 51 | } 52 | }, 53 | new Client 54 | { 55 | ClientId = "js", 56 | ClientName = "JavaScript Client", 57 | AllowedGrantTypes = GrantTypes.Implicit, 58 | AllowAccessTokensViaBrowser = true, 59 | 60 | RedirectUris = { "http://localhost:50001/callback" }, 61 | PostLogoutRedirectUris = { "http://localhost:50001/index.html" }, 62 | AllowedCorsOrigins = { "http://localhost:50001" }, 63 | 64 | AllowedScopes = 65 | { 66 | IdentityServerConstants.StandardScopes.OpenId, 67 | IdentityServerConstants.StandardScopes.Profile, 68 | "api1" 69 | } 70 | } 71 | }; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Blazor.Exteions.Oidc.Test.Identity 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:50000", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "Blazor.Exteions.Oidc.Test.Identity": { 18 | "commandName": "Project", 19 | "environmentVariables": { 20 | "ASPNETCORE_ENVIRONMENT": "Development" 21 | }, 22 | "applicationUrl": "http://localhost:50000" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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.Events; 7 | using IdentityServer4.Extensions; 8 | using IdentityServer4.Models; 9 | using IdentityServer4.Services; 10 | using IdentityServer4.Stores; 11 | using IdentityServer4.Test; 12 | using Microsoft.AspNetCore.Authentication; 13 | using Microsoft.AspNetCore.Authorization; 14 | using Microsoft.AspNetCore.Http; 15 | using Microsoft.AspNetCore.Mvc; 16 | using System; 17 | using System.Linq; 18 | using System.Threading.Tasks; 19 | 20 | namespace IdentityServer4.Quickstart.UI 21 | { 22 | /// 23 | /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. 24 | /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! 25 | /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval 26 | /// 27 | [SecurityHeaders] 28 | [AllowAnonymous] 29 | public class AccountController : Controller 30 | { 31 | private readonly TestUserStore _users; 32 | private readonly IIdentityServerInteractionService _interaction; 33 | private readonly IClientStore _clientStore; 34 | private readonly IAuthenticationSchemeProvider _schemeProvider; 35 | private readonly IEventService _events; 36 | 37 | public AccountController( 38 | IIdentityServerInteractionService interaction, 39 | IClientStore clientStore, 40 | IAuthenticationSchemeProvider schemeProvider, 41 | IEventService events, 42 | TestUserStore users = null) 43 | { 44 | // if the TestUserStore is not in DI, then we'll just use the global users collection 45 | // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity) 46 | _users = users ?? new TestUserStore(TestUsers.Users); 47 | 48 | _interaction = interaction; 49 | _clientStore = clientStore; 50 | _schemeProvider = schemeProvider; 51 | _events = events; 52 | } 53 | 54 | /// 55 | /// Entry point into the login workflow 56 | /// 57 | [HttpGet] 58 | public async Task Login(string returnUrl) 59 | { 60 | // build a model so we know what to show on the login page 61 | var vm = await BuildLoginViewModelAsync(returnUrl); 62 | 63 | if (vm.IsExternalLoginOnly) 64 | { 65 | // we only have one option for logging in and it's an external provider 66 | return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl }); 67 | } 68 | 69 | return View(vm); 70 | } 71 | 72 | /// 73 | /// Handle postback from username/password login 74 | /// 75 | [HttpPost] 76 | [ValidateAntiForgeryToken] 77 | public async Task Login(LoginInputModel model, string button) 78 | { 79 | // check if we are in the context of an authorization request 80 | var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 81 | 82 | // the user clicked the "cancel" button 83 | if (button != "login") 84 | { 85 | if (context != null) 86 | { 87 | // if the user cancels, send a result back into IdentityServer as if they 88 | // denied the consent (even if this client does not require consent). 89 | // this will send back an access denied OIDC error response to the client. 90 | await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); 91 | 92 | // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null 93 | if (await _clientStore.IsPkceClientAsync(context.ClientId)) 94 | { 95 | // if the client is PKCE then we assume it's native, so this change in how to 96 | // return the response is for better UX for the end user. 97 | return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); 98 | } 99 | 100 | return Redirect(model.ReturnUrl); 101 | } 102 | else 103 | { 104 | // since we don't have a valid context, then we just go back to the home page 105 | return Redirect("~/"); 106 | } 107 | } 108 | 109 | if (ModelState.IsValid) 110 | { 111 | // validate username/password against in-memory store 112 | if (_users.ValidateCredentials(model.Username, model.Password)) 113 | { 114 | var user = _users.FindByUsername(model.Username); 115 | await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); 116 | 117 | // only set explicit expiration here if user chooses "remember me". 118 | // otherwise we rely upon expiration configured in cookie middleware. 119 | AuthenticationProperties props = null; 120 | if (AccountOptions.AllowRememberLogin && model.RememberLogin) 121 | { 122 | props = new AuthenticationProperties 123 | { 124 | IsPersistent = true, 125 | ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) 126 | }; 127 | }; 128 | 129 | // issue authentication cookie with subject ID and username 130 | await HttpContext.SignInAsync(user.SubjectId, user.Username, props); 131 | 132 | if (context != null) 133 | { 134 | if (await _clientStore.IsPkceClientAsync(context.ClientId)) 135 | { 136 | // if the client is PKCE then we assume it's native, so this change in how to 137 | // return the response is for better UX for the end user. 138 | return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); 139 | } 140 | 141 | // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null 142 | return Redirect(model.ReturnUrl); 143 | } 144 | 145 | // request for a local page 146 | if (Url.IsLocalUrl(model.ReturnUrl)) 147 | { 148 | return Redirect(model.ReturnUrl); 149 | } 150 | else if (string.IsNullOrEmpty(model.ReturnUrl)) 151 | { 152 | return Redirect("~/"); 153 | } 154 | else 155 | { 156 | // user might have clicked on a malicious link - should be logged 157 | throw new Exception("invalid return URL"); 158 | } 159 | } 160 | 161 | await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); 162 | ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); 163 | } 164 | 165 | // something went wrong, show form with error 166 | var vm = await BuildLoginViewModelAsync(model); 167 | return View(vm); 168 | } 169 | 170 | 171 | /// 172 | /// Show logout page 173 | /// 174 | [HttpGet] 175 | public async Task Logout(string logoutId) 176 | { 177 | // build a model so the logout page knows what to display 178 | var vm = await BuildLogoutViewModelAsync(logoutId); 179 | 180 | if (vm.ShowLogoutPrompt == false) 181 | { 182 | // if the request for logout was properly authenticated from IdentityServer, then 183 | // we don't need to show the prompt and can just log the user out directly. 184 | return await Logout(vm); 185 | } 186 | 187 | return View(vm); 188 | } 189 | 190 | /// 191 | /// Handle logout page postback 192 | /// 193 | [HttpPost] 194 | [ValidateAntiForgeryToken] 195 | public async Task Logout(LogoutInputModel model) 196 | { 197 | // build a model so the logged out page knows what to display 198 | var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); 199 | 200 | if (User?.Identity.IsAuthenticated == true) 201 | { 202 | // delete local authentication cookie 203 | await HttpContext.SignOutAsync(); 204 | 205 | // raise the logout event 206 | await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); 207 | } 208 | 209 | // check if we need to trigger sign-out at an upstream identity provider 210 | if (vm.TriggerExternalSignout) 211 | { 212 | // build a return URL so the upstream provider will redirect back 213 | // to us after the user has logged out. this allows us to then 214 | // complete our single sign-out processing. 215 | string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); 216 | 217 | // this triggers a redirect to the external provider for sign-out 218 | return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); 219 | } 220 | 221 | return View("LoggedOut", vm); 222 | } 223 | 224 | 225 | 226 | /*****************************************/ 227 | /* helper APIs for the AccountController */ 228 | /*****************************************/ 229 | private async Task BuildLoginViewModelAsync(string returnUrl) 230 | { 231 | var context = await _interaction.GetAuthorizationContextAsync(returnUrl); 232 | if (context?.IdP != null) 233 | { 234 | // this is meant to short circuit the UI and only trigger the one external IdP 235 | return new LoginViewModel 236 | { 237 | EnableLocalLogin = false, 238 | ReturnUrl = returnUrl, 239 | Username = context?.LoginHint, 240 | ExternalProviders = new ExternalProvider[] { new ExternalProvider { AuthenticationScheme = context.IdP } } 241 | }; 242 | } 243 | 244 | var schemes = await _schemeProvider.GetAllSchemesAsync(); 245 | 246 | var providers = schemes 247 | .Where(x => x.DisplayName != null || 248 | (x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) 249 | ) 250 | .Select(x => new ExternalProvider 251 | { 252 | DisplayName = x.DisplayName, 253 | AuthenticationScheme = x.Name 254 | }).ToList(); 255 | 256 | var allowLocal = true; 257 | if (context?.ClientId != null) 258 | { 259 | var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); 260 | if (client != null) 261 | { 262 | allowLocal = client.EnableLocalLogin; 263 | 264 | if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) 265 | { 266 | providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); 267 | } 268 | } 269 | } 270 | 271 | return new LoginViewModel 272 | { 273 | AllowRememberLogin = AccountOptions.AllowRememberLogin, 274 | EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, 275 | ReturnUrl = returnUrl, 276 | Username = context?.LoginHint, 277 | ExternalProviders = providers.ToArray() 278 | }; 279 | } 280 | 281 | private async Task BuildLoginViewModelAsync(LoginInputModel model) 282 | { 283 | var vm = await BuildLoginViewModelAsync(model.ReturnUrl); 284 | vm.Username = model.Username; 285 | vm.RememberLogin = model.RememberLogin; 286 | return vm; 287 | } 288 | 289 | private async Task BuildLogoutViewModelAsync(string logoutId) 290 | { 291 | var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; 292 | 293 | if (User?.Identity.IsAuthenticated != true) 294 | { 295 | // if the user is not authenticated, then just show logged out page 296 | vm.ShowLogoutPrompt = false; 297 | return vm; 298 | } 299 | 300 | var context = await _interaction.GetLogoutContextAsync(logoutId); 301 | if (context?.ShowSignoutPrompt == false) 302 | { 303 | // it's safe to automatically sign-out 304 | vm.ShowLogoutPrompt = false; 305 | return vm; 306 | } 307 | 308 | // show the logout prompt. this prevents attacks where the user 309 | // is automatically signed out by another malicious web page. 310 | return vm; 311 | } 312 | 313 | private async Task BuildLoggedOutViewModelAsync(string logoutId) 314 | { 315 | // get context information (client name, post logout redirect URI and iframe for federated signout) 316 | var logout = await _interaction.GetLogoutContextAsync(logoutId); 317 | 318 | var vm = new LoggedOutViewModel 319 | { 320 | AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, 321 | PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, 322 | ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, 323 | SignOutIframeUrl = logout?.SignOutIFrameUrl, 324 | LogoutId = logoutId 325 | }; 326 | 327 | if (User?.Identity.IsAuthenticated == true) 328 | { 329 | var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; 330 | if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) 331 | { 332 | var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp); 333 | if (providerSupportsSignout) 334 | { 335 | if (vm.LogoutId == null) 336 | { 337 | // if there's no current logout context, we need to create one 338 | // this captures necessary info from the current logged in user 339 | // before we signout and redirect away to the external IdP for signout 340 | vm.LogoutId = await _interaction.CreateLogoutContextAsync(); 341 | } 342 | 343 | vm.ExternalAuthenticationScheme = idp; 344 | } 345 | } 346 | } 347 | 348 | return vm; 349 | } 350 | } 351 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | // specify the Windows authentication scheme being used 19 | public static readonly string WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; 20 | // if user uses windows auth, should we load the groups from windows 21 | public static bool IncludeWindowsGroups = false; 22 | 23 | public static string InvalidCredentialsErrorMessage = "Invalid username or password"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Quickstart/Account/ExternalController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Claims; 5 | using System.Security.Principal; 6 | using System.Threading.Tasks; 7 | using IdentityModel; 8 | using IdentityServer4.Events; 9 | using IdentityServer4.Quickstart.UI; 10 | using IdentityServer4.Services; 11 | using IdentityServer4.Stores; 12 | using IdentityServer4.Test; 13 | using Microsoft.AspNetCore.Authentication; 14 | using Microsoft.AspNetCore.Authorization; 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.AspNetCore.Mvc; 17 | 18 | namespace Host.Quickstart.Account 19 | { 20 | [SecurityHeaders] 21 | [AllowAnonymous] 22 | public class ExternalController : Controller 23 | { 24 | private readonly TestUserStore _users; 25 | private readonly IIdentityServerInteractionService _interaction; 26 | private readonly IClientStore _clientStore; 27 | private readonly IEventService _events; 28 | 29 | public ExternalController( 30 | IIdentityServerInteractionService interaction, 31 | IClientStore clientStore, 32 | IEventService events, 33 | TestUserStore users = null) 34 | { 35 | // if the TestUserStore is not in DI, then we'll just use the global users collection 36 | // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity) 37 | _users = users ?? new TestUserStore(TestUsers.Users); 38 | 39 | _interaction = interaction; 40 | _clientStore = clientStore; 41 | _events = events; 42 | } 43 | 44 | /// 45 | /// initiate roundtrip to external authentication provider 46 | /// 47 | [HttpGet] 48 | public async Task Challenge(string provider, string returnUrl) 49 | { 50 | if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; 51 | 52 | // validate returnUrl - either it is a valid OIDC URL or back to a local page 53 | if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) 54 | { 55 | // user might have clicked on a malicious link - should be logged 56 | throw new Exception("invalid return URL"); 57 | } 58 | 59 | if (AccountOptions.WindowsAuthenticationSchemeName == provider) 60 | { 61 | // windows authentication needs special handling 62 | return await ProcessWindowsLoginAsync(returnUrl); 63 | } 64 | else 65 | { 66 | // start challenge and roundtrip the return URL and scheme 67 | var props = new AuthenticationProperties 68 | { 69 | RedirectUri = Url.Action(nameof(Callback)), 70 | Items = 71 | { 72 | { "returnUrl", returnUrl }, 73 | { "scheme", provider }, 74 | } 75 | }; 76 | 77 | return Challenge(props, provider); 78 | } 79 | } 80 | 81 | /// 82 | /// Post processing of external authentication 83 | /// 84 | [HttpGet] 85 | public async Task Callback() 86 | { 87 | // read external identity from the temporary cookie 88 | var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); 89 | if (result?.Succeeded != true) 90 | { 91 | throw new Exception("External authentication error"); 92 | } 93 | 94 | // lookup our user and external provider info 95 | var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result); 96 | if (user == null) 97 | { 98 | // this might be where you might initiate a custom workflow for user registration 99 | // in this sample we don't show how that would be done, as our sample implementation 100 | // simply auto-provisions new external user 101 | user = AutoProvisionUser(provider, providerUserId, claims); 102 | } 103 | 104 | // this allows us to collect any additonal claims or properties 105 | // for the specific prtotocols used and store them in the local auth cookie. 106 | // this is typically used to store data needed for signout from those protocols. 107 | var additionalLocalClaims = new List(); 108 | var localSignInProps = new AuthenticationProperties(); 109 | ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps); 110 | ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps); 111 | ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps); 112 | 113 | // issue authentication cookie for user 114 | await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username)); 115 | await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, localSignInProps, additionalLocalClaims.ToArray()); 116 | 117 | // delete temporary cookie used during external authentication 118 | await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); 119 | 120 | // retrieve return URL 121 | var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; 122 | 123 | // check if external login is in the context of an OIDC request 124 | var context = await _interaction.GetAuthorizationContextAsync(returnUrl); 125 | if (context != null) 126 | { 127 | if (await _clientStore.IsPkceClientAsync(context.ClientId)) 128 | { 129 | // if the client is PKCE then we assume it's native, so this change in how to 130 | // return the response is for better UX for the end user. 131 | return View("Redirect", new RedirectViewModel { RedirectUrl = returnUrl }); 132 | } 133 | } 134 | 135 | return Redirect(returnUrl); 136 | } 137 | 138 | private async Task ProcessWindowsLoginAsync(string returnUrl) 139 | { 140 | // see if windows auth has already been requested and succeeded 141 | var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName); 142 | if (result?.Principal is WindowsPrincipal wp) 143 | { 144 | // we will issue the external cookie and then redirect the 145 | // user back to the external callback, in essence, treating windows 146 | // auth the same as any other external authentication mechanism 147 | var props = new AuthenticationProperties() 148 | { 149 | RedirectUri = Url.Action("Callback"), 150 | Items = 151 | { 152 | { "returnUrl", returnUrl }, 153 | { "scheme", AccountOptions.WindowsAuthenticationSchemeName }, 154 | } 155 | }; 156 | 157 | var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName); 158 | id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name)); 159 | id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name)); 160 | 161 | // add the groups as claims -- be careful if the number of groups is too large 162 | if (AccountOptions.IncludeWindowsGroups) 163 | { 164 | var wi = wp.Identity as WindowsIdentity; 165 | var groups = wi.Groups.Translate(typeof(NTAccount)); 166 | var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value)); 167 | id.AddClaims(roles); 168 | } 169 | 170 | await HttpContext.SignInAsync( 171 | IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme, 172 | new ClaimsPrincipal(id), 173 | props); 174 | return Redirect(props.RedirectUri); 175 | } 176 | else 177 | { 178 | // trigger windows auth 179 | // since windows auth don't support the redirect uri, 180 | // this URL is re-triggered when we call challenge 181 | return Challenge(AccountOptions.WindowsAuthenticationSchemeName); 182 | } 183 | } 184 | 185 | private (TestUser user, string provider, string providerUserId, IEnumerable claims) FindUserFromExternalProvider(AuthenticateResult result) 186 | { 187 | var externalUser = result.Principal; 188 | 189 | // try to determine the unique id of the external user (issued by the provider) 190 | // the most common claim type for that are the sub claim and the NameIdentifier 191 | // depending on the external provider, some other claim type might be used 192 | var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? 193 | externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? 194 | throw new Exception("Unknown userid"); 195 | 196 | // remove the user id claim so we don't include it as an extra claim if/when we provision the user 197 | var claims = externalUser.Claims.ToList(); 198 | claims.Remove(userIdClaim); 199 | 200 | var provider = result.Properties.Items["scheme"]; 201 | var providerUserId = userIdClaim.Value; 202 | 203 | // find external user 204 | var user = _users.FindByExternalProvider(provider, providerUserId); 205 | 206 | return (user, provider, providerUserId, claims); 207 | } 208 | 209 | private TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable claims) 210 | { 211 | var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList()); 212 | return user; 213 | } 214 | 215 | private void ProcessLoginCallbackForOidc(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) 216 | { 217 | // if the external system sent a session id claim, copy it over 218 | // so we can use it for single sign-out 219 | var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); 220 | if (sid != null) 221 | { 222 | localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); 223 | } 224 | 225 | // if the external provider issued an id_token, we'll keep it for signout 226 | var id_token = externalResult.Properties.GetTokenValue("id_token"); 227 | if (id_token != null) 228 | { 229 | localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } }); 230 | } 231 | } 232 | 233 | private void ProcessLoginCallbackForWsFed(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) 234 | { 235 | } 236 | 237 | private void ProcessLoginCallbackForSaml2p(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) 238 | { 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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; } = false; 14 | 15 | public string LogoutId { get; set; } 16 | public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; 17 | public string ExternalAuthenticationScheme { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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; } = true; 14 | public bool EnableLocalLogin { get; set; } = true; 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 => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; 21 | } 22 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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; } = true; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Quickstart/Account/RedirectViewModel.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 | 6 | namespace IdentityServer4.Quickstart.UI 7 | { 8 | public class RedirectViewModel 9 | { 10 | public string RedirectUrl { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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.Events; 6 | using IdentityServer4.Models; 7 | using IdentityServer4.Services; 8 | using IdentityServer4.Stores; 9 | using IdentityServer4.Extensions; 10 | using Microsoft.AspNetCore.Authorization; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Microsoft.Extensions.Logging; 13 | using System.Linq; 14 | using System.Threading.Tasks; 15 | 16 | namespace IdentityServer4.Quickstart.UI 17 | { 18 | /// 19 | /// This controller processes the consent UI 20 | /// 21 | [SecurityHeaders] 22 | [Authorize] 23 | public class ConsentController : Controller 24 | { 25 | private readonly IIdentityServerInteractionService _interaction; 26 | private readonly IClientStore _clientStore; 27 | private readonly IResourceStore _resourceStore; 28 | private readonly IEventService _events; 29 | private readonly ILogger _logger; 30 | 31 | public ConsentController( 32 | IIdentityServerInteractionService interaction, 33 | IClientStore clientStore, 34 | IResourceStore resourceStore, 35 | IEventService events, 36 | ILogger logger) 37 | { 38 | _interaction = interaction; 39 | _clientStore = clientStore; 40 | _resourceStore = resourceStore; 41 | _events = events; 42 | _logger = logger; 43 | } 44 | 45 | /// 46 | /// Shows the consent screen 47 | /// 48 | /// 49 | /// 50 | [HttpGet] 51 | public async Task Index(string returnUrl) 52 | { 53 | var vm = await BuildViewModelAsync(returnUrl); 54 | if (vm != null) 55 | { 56 | return View("Index", vm); 57 | } 58 | 59 | return View("Error"); 60 | } 61 | 62 | /// 63 | /// Handles the consent screen postback 64 | /// 65 | [HttpPost] 66 | [ValidateAntiForgeryToken] 67 | public async Task Index(ConsentInputModel model) 68 | { 69 | var result = await ProcessConsent(model); 70 | 71 | if (result.IsRedirect) 72 | { 73 | if (await _clientStore.IsPkceClientAsync(result.ClientId)) 74 | { 75 | // if the client is PKCE then we assume it's native, so this change in how to 76 | // return the response is for better UX for the end user. 77 | return View("Redirect", new RedirectViewModel { RedirectUrl = result.RedirectUri }); 78 | } 79 | 80 | return Redirect(result.RedirectUri); 81 | } 82 | 83 | if (result.HasValidationError) 84 | { 85 | ModelState.AddModelError("", result.ValidationError); 86 | } 87 | 88 | if (result.ShowView) 89 | { 90 | return View("Index", result.ViewModel); 91 | } 92 | 93 | return View("Error"); 94 | } 95 | 96 | /*****************************************/ 97 | /* helper APIs for the ConsentController */ 98 | /*****************************************/ 99 | private async Task ProcessConsent(ConsentInputModel model) 100 | { 101 | var result = new ProcessConsentResult(); 102 | 103 | // validate return url is still valid 104 | var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 105 | if (request == null) return result; 106 | 107 | ConsentResponse grantedConsent = null; 108 | 109 | // user clicked 'no' - send back the standard 'access_denied' response 110 | if (model.Button == "no") 111 | { 112 | grantedConsent = ConsentResponse.Denied; 113 | 114 | // emit event 115 | await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), result.ClientId, request.ScopesRequested)); 116 | } 117 | // user clicked 'yes' - validate the data 118 | else if (model.Button == "yes" && model != null) 119 | { 120 | // if the user consented to some scope, build the response model 121 | if (model.ScopesConsented != null && model.ScopesConsented.Any()) 122 | { 123 | var scopes = model.ScopesConsented; 124 | if (ConsentOptions.EnableOfflineAccess == false) 125 | { 126 | scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); 127 | } 128 | 129 | grantedConsent = new ConsentResponse 130 | { 131 | RememberConsent = model.RememberConsent, 132 | ScopesConsented = scopes.ToArray() 133 | }; 134 | 135 | // emit event 136 | await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested, grantedConsent.ScopesConsented, grantedConsent.RememberConsent)); 137 | } 138 | else 139 | { 140 | result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; 141 | } 142 | } 143 | else 144 | { 145 | result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; 146 | } 147 | 148 | if (grantedConsent != null) 149 | { 150 | // communicate outcome of consent back to identityserver 151 | await _interaction.GrantConsentAsync(request, grantedConsent); 152 | 153 | // indicate that's it ok to redirect back to authorization endpoint 154 | result.RedirectUri = model.ReturnUrl; 155 | result.ClientId = request.ClientId; 156 | } 157 | else 158 | { 159 | // we need to redisplay the consent UI 160 | result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); 161 | } 162 | 163 | return result; 164 | } 165 | 166 | private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) 167 | { 168 | var request = await _interaction.GetAuthorizationContextAsync(returnUrl); 169 | if (request != null) 170 | { 171 | var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); 172 | if (client != null) 173 | { 174 | var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); 175 | if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) 176 | { 177 | return CreateConsentViewModel(model, returnUrl, request, client, resources); 178 | } 179 | else 180 | { 181 | _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); 182 | } 183 | } 184 | else 185 | { 186 | _logger.LogError("Invalid client id: {0}", request.ClientId); 187 | } 188 | } 189 | else 190 | { 191 | _logger.LogError("No consent request matching request: {0}", returnUrl); 192 | } 193 | 194 | return null; 195 | } 196 | 197 | private ConsentViewModel CreateConsentViewModel( 198 | ConsentInputModel model, string returnUrl, 199 | AuthorizationRequest request, 200 | Client client, Resources resources) 201 | { 202 | var vm = new ConsentViewModel 203 | { 204 | RememberConsent = model?.RememberConsent ?? true, 205 | ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), 206 | 207 | ReturnUrl = returnUrl, 208 | 209 | ClientName = client.ClientName ?? client.ClientId, 210 | ClientUrl = client.ClientUri, 211 | ClientLogoUrl = client.LogoUri, 212 | AllowRememberConsent = client.AllowRememberConsent 213 | }; 214 | 215 | vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 216 | vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 217 | if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess) 218 | { 219 | vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] { 220 | GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null) 221 | }); 222 | } 223 | 224 | return vm; 225 | } 226 | 227 | private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) 228 | { 229 | return new ScopeViewModel 230 | { 231 | Name = identity.Name, 232 | DisplayName = identity.DisplayName, 233 | Description = identity.Description, 234 | Emphasize = identity.Emphasize, 235 | Required = identity.Required, 236 | Checked = check || identity.Required 237 | }; 238 | } 239 | 240 | public ScopeViewModel CreateScopeViewModel(Scope scope, bool check) 241 | { 242 | return new ScopeViewModel 243 | { 244 | Name = scope.Name, 245 | DisplayName = scope.DisplayName, 246 | Description = scope.Description, 247 | Emphasize = scope.Emphasize, 248 | Required = scope.Required, 249 | Checked = check || scope.Required 250 | }; 251 | } 252 | 253 | private ScopeViewModel GetOfflineAccessScope(bool check) 254 | { 255 | return new ScopeViewModel 256 | { 257 | Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, 258 | DisplayName = ConsentOptions.OfflineAccessDisplayName, 259 | Description = ConsentOptions.OfflineAccessDescription, 260 | Emphasize = true, 261 | Checked = check 262 | }; 263 | } 264 | } 265 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | public string ClientId { get; set; } 12 | 13 | public bool ShowView => ViewModel != null; 14 | public ConsentViewModel ViewModel { get; set; } 15 | 16 | public bool HasValidationError => ValidationError != null; 17 | public string ValidationError { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Quickstart/Diagnostics/DiagnosticsController.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.Linq; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Authentication; 8 | using Microsoft.AspNetCore.Authorization; 9 | using Microsoft.AspNetCore.Mvc; 10 | 11 | namespace IdentityServer4.Quickstart.UI 12 | { 13 | [SecurityHeaders] 14 | [Authorize] 15 | public class DiagnosticsController : Controller 16 | { 17 | public async Task Index() 18 | { 19 | var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; 20 | if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) 21 | { 22 | return NotFound(); 23 | } 24 | 25 | var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); 26 | return View(model); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Quickstart/Diagnostics/DiagnosticsViewModel.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 Microsoft.AspNetCore.Authentication; 7 | using Newtonsoft.Json; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | 11 | namespace IdentityServer4.Quickstart.UI 12 | { 13 | public class DiagnosticsViewModel 14 | { 15 | public DiagnosticsViewModel(AuthenticateResult result) 16 | { 17 | AuthenticateResult = result; 18 | 19 | if (result.Properties.Items.ContainsKey("client_list")) 20 | { 21 | var encoded = result.Properties.Items["client_list"]; 22 | var bytes = Base64Url.Decode(encoded); 23 | var value = Encoding.UTF8.GetString(bytes); 24 | 25 | Clients = JsonConvert.DeserializeObject(value); 26 | } 27 | } 28 | 29 | public AuthenticateResult AuthenticateResult { get; } 30 | public IEnumerable Clients { get; } = new List(); 31 | } 32 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Quickstart/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using IdentityServer4.Stores; 3 | 4 | namespace IdentityServer4.Quickstart.UI 5 | { 6 | public static class Extensions 7 | { 8 | /// 9 | /// Determines whether the client is configured to use PKCE. 10 | /// 11 | /// The store. 12 | /// The client identifier. 13 | /// 14 | public static async Task IsPkceClientAsync(this IClientStore store, string client_id) 15 | { 16 | if (!string.IsNullOrWhiteSpace(client_id)) 17 | { 18 | var client = await store.FindEnabledClientByIdAsync(client_id); 19 | return client?.RequirePkce == true; 20 | } 21 | 22 | return false; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | using IdentityServer4.Events; 13 | using IdentityServer4.Extensions; 14 | 15 | namespace IdentityServer4.Quickstart.UI 16 | { 17 | /// 18 | /// This sample controller allows a user to revoke grants given to clients 19 | /// 20 | [SecurityHeaders] 21 | [Authorize] 22 | public class GrantsController : Controller 23 | { 24 | private readonly IIdentityServerInteractionService _interaction; 25 | private readonly IClientStore _clients; 26 | private readonly IResourceStore _resources; 27 | private readonly IEventService _events; 28 | 29 | public GrantsController(IIdentityServerInteractionService interaction, 30 | IClientStore clients, 31 | IResourceStore resources, 32 | IEventService events) 33 | { 34 | _interaction = interaction; 35 | _clients = clients; 36 | _resources = resources; 37 | _events = events; 38 | } 39 | 40 | /// 41 | /// Show list of grants 42 | /// 43 | [HttpGet] 44 | public async Task Index() 45 | { 46 | return View("Index", await BuildViewModelAsync()); 47 | } 48 | 49 | /// 50 | /// Handle postback to revoke a client 51 | /// 52 | [HttpPost] 53 | [ValidateAntiForgeryToken] 54 | public async Task Revoke(string clientId) 55 | { 56 | await _interaction.RevokeUserConsentAsync(clientId); 57 | await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); 58 | 59 | return RedirectToAction("Index"); 60 | } 61 | 62 | private async Task BuildViewModelAsync() 63 | { 64 | var grants = await _interaction.GetAllUserConsentsAsync(); 65 | 66 | var list = new List(); 67 | foreach(var grant in grants) 68 | { 69 | var client = await _clients.FindClientByIdAsync(grant.ClientId); 70 | if (client != null) 71 | { 72 | var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); 73 | 74 | var item = new GrantViewModel() 75 | { 76 | ClientId = client.ClientId, 77 | ClientName = client.ClientName ?? client.ClientId, 78 | ClientLogoUrl = client.LogoUri, 79 | ClientUrl = client.ClientUri, 80 | Created = grant.CreationTime, 81 | Expires = grant.Expiration, 82 | IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), 83 | ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() 84 | }; 85 | 86 | list.Add(item); 87 | } 88 | } 89 | 90 | return new GrantsViewModel 91 | { 92 | Grants = list 93 | }; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Quickstart/Grants/GrantsViewModel.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 | 8 | namespace IdentityServer4.Quickstart.UI 9 | { 10 | public class GrantsViewModel 11 | { 12 | public IEnumerable Grants { get; set; } 13 | } 14 | 15 | public class GrantViewModel 16 | { 17 | public string ClientId { get; set; } 18 | public string ClientName { get; set; } 19 | public string ClientUrl { get; set; } 20 | public string ClientLogoUrl { get; set; } 21 | public DateTime Created { get; set; } 22 | public DateTime? Expires { get; set; } 23 | public IEnumerable IdentityGrantNames { get; set; } 24 | public IEnumerable ApiGrantNames { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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.Authorization; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Mvc; 9 | using System.Threading.Tasks; 10 | 11 | namespace IdentityServer4.Quickstart.UI 12 | { 13 | [SecurityHeaders] 14 | [AllowAnonymous] 15 | public class HomeController : Controller 16 | { 17 | private readonly IIdentityServerInteractionService _interaction; 18 | private readonly IHostingEnvironment _environment; 19 | 20 | public HomeController(IIdentityServerInteractionService interaction, IHostingEnvironment environment) 21 | { 22 | _interaction = interaction; 23 | _environment = environment; 24 | } 25 | 26 | public IActionResult Index() 27 | { 28 | if (_environment.IsDevelopment()) 29 | { 30 | // only show in development 31 | return View(); 32 | } 33 | 34 | return NotFound(); 35 | } 36 | 37 | /// 38 | /// Shows the error page 39 | /// 40 | public async Task Error(string errorId) 41 | { 42 | var vm = new ErrorViewModel(); 43 | 44 | // retrieve error details from identityserver 45 | var message = await _interaction.GetErrorContextAsync(errorId); 46 | if (message != null) 47 | { 48 | vm.Error = message; 49 | 50 | if (!_environment.IsDevelopment()) 51 | { 52 | // only show in development 53 | message.ErrorDescription = null; 54 | } 55 | } 56 | 57 | return View("Error", vm); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Quickstart/TestUsers.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.Test; 7 | using System.Collections.Generic; 8 | using System.Security.Claims; 9 | 10 | namespace IdentityServer4.Quickstart.UI 11 | { 12 | public class TestUsers 13 | { 14 | public static List Users = new List 15 | { 16 | new TestUser{SubjectId = "818727", Username = "alice", Password = "alice", 17 | Claims = 18 | { 19 | new Claim(JwtClaimTypes.Name, "Alice Smith"), 20 | new Claim(JwtClaimTypes.GivenName, "Alice"), 21 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 22 | new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), 23 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 24 | new Claim(JwtClaimTypes.WebSite, "http://alice.com"), 25 | new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json) 26 | } 27 | }, 28 | new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob", 29 | Claims = 30 | { 31 | new Claim(JwtClaimTypes.Name, "Bob Smith"), 32 | new Claim(JwtClaimTypes.GivenName, "Bob"), 33 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 34 | new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), 35 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 36 | new Claim(JwtClaimTypes.WebSite, "http://bob.com"), 37 | new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json), 38 | new Claim("location", "somewhere") 39 | } 40 | } 41 | }; 42 | } 43 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Blazor.Extensions.Oidc.Test.Identity; 6 | using IdentityServer4.Quickstart.UI; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.Extensions.DependencyInjection; 11 | 12 | namespace Blazor.Exteions.Oidc.Test.Identity 13 | { 14 | public class Startup 15 | { 16 | // This method gets called by the runtime. Use this method to add services to the container. 17 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 18 | public void ConfigureServices(IServiceCollection services) 19 | { 20 | services.AddMvc(); 21 | 22 | services.AddIdentityServer() 23 | .AddDeveloperSigningCredential() 24 | .AddInMemoryApiResources(Configuration.GetApiResources()) 25 | .AddInMemoryIdentityResources(Configuration.GetIdentityResources()) 26 | .AddTestUsers(TestUsers.Users) 27 | .AddInMemoryClients(Configuration.GetClients()); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.UseStaticFiles(); 39 | 40 | app.UseIdentityServer(); 41 | 42 | app.UseMvcWithDefaultRoute(); 43 | 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginViewModel 2 | 3 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 |
-------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/Consent/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model ConsentViewModel 2 | 3 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 |
  • -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/Diagnostics/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model DiagnosticsViewModel 2 | 3 |

    Authentication cookie

    4 | 5 |

    Claims

    6 |
    7 | @foreach (var claim in Model.AuthenticateResult.Principal.Claims) 8 | { 9 |
    @claim.Type
    10 |
    @claim.Value
    11 | } 12 |
    13 | 14 |

    Properties

    15 |
    16 | @foreach (var prop in Model.AuthenticateResult.Properties.Items) 17 | { 18 |
    @prop.Key
    19 |
    @prop.Value
    20 | } 21 |
    22 | 23 | @if (Model.Clients.Any()) 24 | { 25 |

    Clients

    26 |
      27 | @foreach (var client in Model.Clients) 28 | { 29 |
    • @client
    • 30 | } 31 |
    32 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 |
    -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | 3 | @{ 4 | var error = Model?.Error?.Error; 5 | var errorDescription = Model?.Error?.ErrorDescription; 6 | var request_id = Model?.Error?.RequestId; 7 | } 8 | 9 |
    10 | 13 | 14 |
    15 |
    16 |
    17 | Sorry, there was an error 18 | 19 | @if (error != null) 20 | { 21 | 22 | 23 | : @error 24 | 25 | 26 | 27 | if (errorDescription != null) 28 | { 29 |
    @errorDescription
    30 | } 31 | } 32 |
    33 | 34 | @if (request_id != null) 35 | { 36 |
    Request Id: @request_id
    37 | } 38 |
    39 |
    40 |
    41 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/Shared/Redirect.cshtml: -------------------------------------------------------------------------------- 1 | @model RedirectViewModel 2 | 3 |

    You are now being returned to the application.

    4 |

    Once complete, you may close this tab

    5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/Shared/_ValidationSummary.cshtml: -------------------------------------------------------------------------------- 1 | @if (ViewContext.ModelState.IsValid == false) 2 | { 3 |
    4 | Error 5 |
    6 |
    7 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer4.Quickstart.UI 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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:'';} -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Authfix/Blazor-Oidc/067b678ccac0871bb9c8c5ad154f6a382bc7ed15/test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/favicon.ico -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Authfix/Blazor-Oidc/067b678ccac0871bb9c8c5ad154f6a382bc7ed15/test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/icon.jpg -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Authfix/Blazor-Oidc/067b678ccac0871bb9c8c5ad154f6a382bc7ed15/test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/icon.png -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/js/signin-redirect.js: -------------------------------------------------------------------------------- 1 | window.location.href = document.querySelector("meta[http-equiv=refresh]").getAttribute("data-url"); 2 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Authfix/Blazor-Oidc/067b678ccac0871bb9c8c5ad154f6a382bc7ed15/test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Authfix/Blazor-Oidc/067b678ccac0871bb9c8c5ad154f6a382bc7ed15/test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Authfix/Blazor-Oidc/067b678ccac0871bb9c8c5ad154f6a382bc7ed15/test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Authfix/Blazor-Oidc/067b678ccac0871bb9c8c5ad154f6a382bc7ed15/test/Blazor.Extensions.Oidc.Test.Identity/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Identity/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); -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Server/Blazor.Extensions.Oidc.Test.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 7.3 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Server/Controllers/SampleDataController.cs: -------------------------------------------------------------------------------- 1 | using Blazor.Extensions.Oidc.Test.Shared; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Blazor.Extensions.Oidc.Test.Server.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class SampleDataController : Controller 12 | { 13 | private static string[] Summaries = new[] 14 | { 15 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 16 | }; 17 | 18 | [HttpGet("[action]")] 19 | public IEnumerable WeatherForecasts() 20 | { 21 | var rng = new Random(); 22 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 23 | { 24 | Date = DateTime.Now.AddDays(index), 25 | TemperatureC = rng.Next(-20, 55), 26 | Summary = Summaries[rng.Next(Summaries.Length)] 27 | }); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Blazor.Extensions.Oidc.Test.Server 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | BuildWebHost(args).Run(); 12 | } 13 | 14 | public static IWebHost BuildWebHost(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .UseConfiguration(new ConfigurationBuilder() 17 | .AddCommandLine(args) 18 | .Build()) 19 | .UseStartup() 20 | .Build(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:50001/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Blazor.Extensions.Oidc.Test.Server": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:50001/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Server/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Server; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.ResponseCompression; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Newtonsoft.Json.Serialization; 7 | using System.Linq; 8 | using System.Net.Mime; 9 | 10 | namespace Blazor.Extensions.Oidc.Test.Server 11 | { 12 | public class Startup 13 | { 14 | // This method gets called by the runtime. Use this method to add services to the container. 15 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | services.AddMvc(); 19 | 20 | services.AddResponseCompression(options => 21 | { 22 | options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] 23 | { 24 | MediaTypeNames.Application.Octet, 25 | WasmMediaTypeNames.Application.Wasm, 26 | }); 27 | }); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 32 | { 33 | app.UseResponseCompression(); 34 | 35 | if (env.IsDevelopment()) 36 | { 37 | app.UseDeveloperExceptionPage(); 38 | } 39 | 40 | app.UseMvc(routes => 41 | { 42 | routes.MapRoute(name: "default", template: "{controller}/{action}/{id?}"); 43 | }); 44 | 45 | app.UseBlazor(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Shared/Blazor.Extensions.Oidc.Test.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 7.3 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/Blazor.Extensions.Oidc.Test.Shared/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Blazor.Extensions.Oidc.Test.Shared 6 | { 7 | public class WeatherForecast 8 | { 9 | public DateTime Date { get; set; } 10 | public int TemperatureC { get; set; } 11 | public string Summary { get; set; } 12 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "2.1.300" 4 | } 5 | } 6 | --------------------------------------------------------------------------------