From 85cd111f5f357fae734113ad35e3b2a19f90c457 Mon Sep 17 00:00:00 2001
From: Muhammad Azeez
Date: Sat, 16 Jan 2021 21:20:05 +0300
Subject: [PATCH] add a sample AuthorizationServer to the repo and put
traffic-police app in a separate folder
---
.../OidcSamples.AuthorizationServer/Config.cs | 111 ++
.../OidcSamples.AuthorizationServer.csproj | 14 +
.../Program.cs | 53 +
.../Properties/launchSettings.json | 12 +
.../Quickstart/Account/AccountController.cs | 368 ++++++
.../Quickstart/Account/AccountOptions.cs | 20 +
.../Quickstart/Account/ExternalController.cs | 196 +++
.../Quickstart/Account/ExternalProvider.cs | 12 +
.../Quickstart/Account/LoggedOutViewModel.cs | 19 +
.../Quickstart/Account/LoginInputModel.cs | 18 +
.../Quickstart/Account/LoginViewModel.cs | 22 +
.../Quickstart/Account/LogoutInputModel.cs | 11 +
.../Quickstart/Account/LogoutViewModel.cs | 11 +
.../Quickstart/Account/RedirectViewModel.cs | 12 +
.../Quickstart/Consent/ConsentController.cs | 262 ++++
.../Quickstart/Consent/ConsentInputModel.cs | 17 +
.../Quickstart/Consent/ConsentOptions.cs | 16 +
.../Quickstart/Consent/ConsentViewModel.cs | 19 +
.../Consent/ProcessConsentResult.cs | 21 +
.../Quickstart/Consent/ScopeViewModel.cs | 16 +
.../Device/DeviceAuthorizationInputModel.cs | 11 +
.../Device/DeviceAuthorizationViewModel.cs | 12 +
.../Quickstart/Device/DeviceController.cs | 232 ++++
.../Diagnostics/DiagnosticsController.cs | 29 +
.../Diagnostics/DiagnosticsViewModel.cs | 32 +
.../Quickstart/Extensions.cs | 27 +
.../Quickstart/Grants/GrantsController.cs | 97 ++
.../Quickstart/Grants/GrantsViewModel.cs | 27 +
.../Quickstart/Home/ErrorViewModel.cs | 22 +
.../Quickstart/Home/HomeController.cs | 65 +
.../Quickstart/SecurityHeadersAttribute.cs | 56 +
.../Quickstart/TestUsers.cs | 38 +
.../Startup.cs | 60 +
.../Views/Account/AccessDenied.cshtml | 7 +
.../Views/Account/LoggedOut.cshtml | 34 +
.../Views/Account/Login.cshtml | 87 ++
.../Views/Account/Logout.cshtml | 15 +
.../Views/Consent/Index.cshtml | 104 ++
.../Views/Device/Success.cshtml | 7 +
.../Views/Device/UserCodeCapture.cshtml | 23 +
.../Views/Device/UserCodeConfirmation.cshtml | 108 ++
.../Views/Diagnostics/Index.cshtml | 64 +
.../Views/Grants/Index.cshtml | 87 ++
.../Views/Home/Index.cshtml | 32 +
.../Views/Shared/Error.cshtml | 40 +
.../Views/Shared/Redirect.cshtml | 11 +
.../Views/Shared/_Layout.cshtml | 28 +
.../Views/Shared/_Nav.cshtml | 33 +
.../Views/Shared/_ScopeListItem.cshtml | 34 +
.../Views/Shared/_ValidationSummary.cshtml | 7 +
.../Views/_ViewImports.cshtml | 2 +
.../Views/_ViewStart.cshtml | 3 +
.../tempkey.jwk | 1 +
.../wwwroot/css/site.css | 24 +
.../wwwroot/css/site.min.css | 1 +
.../wwwroot/css/site.scss | 42 +
.../wwwroot/favicon.ico | Bin 0 -> 1150 bytes
.../wwwroot/icon.jpg | Bin 0 -> 19482 bytes
.../wwwroot/icon.png | Bin 0 -> 20796 bytes
.../wwwroot/js/signin-redirect.js | 1 +
.../wwwroot/js/signout-redirect.js | 6 +
.../wwwroot/lib/bootstrap/README.md | 209 +++
.../wwwroot/lib/bootstrap/scss/_alert.scss | 51 +
.../wwwroot/lib/bootstrap/scss/_badge.scss | 54 +
.../lib/bootstrap/scss/_breadcrumb.scss | 42 +
.../lib/bootstrap/scss/_button-group.scss | 163 +++
.../wwwroot/lib/bootstrap/scss/_buttons.scss | 139 ++
.../wwwroot/lib/bootstrap/scss/_card.scss | 278 ++++
.../wwwroot/lib/bootstrap/scss/_carousel.scss | 197 +++
.../wwwroot/lib/bootstrap/scss/_close.scss | 41 +
.../wwwroot/lib/bootstrap/scss/_code.scss | 48 +
.../lib/bootstrap/scss/_custom-forms.scss | 521 ++++++++
.../wwwroot/lib/bootstrap/scss/_dropdown.scss | 191 +++
.../wwwroot/lib/bootstrap/scss/_forms.scss | 338 +++++
.../lib/bootstrap/scss/_functions.scss | 134 ++
.../wwwroot/lib/bootstrap/scss/_grid.scss | 69 +
.../wwwroot/lib/bootstrap/scss/_images.scss | 42 +
.../lib/bootstrap/scss/_input-group.scss | 191 +++
.../lib/bootstrap/scss/_jumbotron.scss | 17 +
.../lib/bootstrap/scss/_list-group.scss | 158 +++
.../wwwroot/lib/bootstrap/scss/_media.scss | 8 +
.../wwwroot/lib/bootstrap/scss/_mixins.scss | 47 +
.../wwwroot/lib/bootstrap/scss/_modal.scss | 239 ++++
.../wwwroot/lib/bootstrap/scss/_nav.scss | 120 ++
.../wwwroot/lib/bootstrap/scss/_navbar.scss | 324 +++++
.../lib/bootstrap/scss/_pagination.scss | 73 ++
.../wwwroot/lib/bootstrap/scss/_popover.scss | 170 +++
.../wwwroot/lib/bootstrap/scss/_print.scss | 141 ++
.../wwwroot/lib/bootstrap/scss/_progress.scss | 46 +
.../wwwroot/lib/bootstrap/scss/_reboot.scss | 482 +++++++
.../wwwroot/lib/bootstrap/scss/_root.scss | 20 +
.../wwwroot/lib/bootstrap/scss/_spinners.scss | 55 +
.../wwwroot/lib/bootstrap/scss/_tables.scss | 185 +++
.../wwwroot/lib/bootstrap/scss/_toasts.scss | 44 +
.../wwwroot/lib/bootstrap/scss/_tooltip.scss | 115 ++
.../lib/bootstrap/scss/_transitions.scss | 20 +
.../wwwroot/lib/bootstrap/scss/_type.scss | 125 ++
.../lib/bootstrap/scss/_utilities.scss | 17 +
.../lib/bootstrap/scss/_variables.scss | 1143 +++++++++++++++++
.../lib/bootstrap/scss/bootstrap-grid.scss | 29 +
.../lib/bootstrap/scss/bootstrap-reboot.scss | 12 +
.../wwwroot/lib/bootstrap/scss/bootstrap.scss | 44 +
.../lib/bootstrap/scss/mixins/_alert.scss | 13 +
.../scss/mixins/_background-variant.scss | 22 +
.../lib/bootstrap/scss/mixins/_badge.scss | 17 +
.../bootstrap/scss/mixins/_border-radius.scss | 63 +
.../bootstrap/scss/mixins/_box-shadow.scss | 20 +
.../bootstrap/scss/mixins/_breakpoints.scss | 123 ++
.../lib/bootstrap/scss/mixins/_buttons.scss | 110 ++
.../lib/bootstrap/scss/mixins/_caret.scss | 62 +
.../lib/bootstrap/scss/mixins/_clearfix.scss | 7 +
.../lib/bootstrap/scss/mixins/_deprecate.scss | 10 +
.../lib/bootstrap/scss/mixins/_float.scss | 14 +
.../lib/bootstrap/scss/mixins/_forms.scss | 177 +++
.../lib/bootstrap/scss/mixins/_gradients.scss | 45 +
.../scss/mixins/_grid-framework.scss | 71 +
.../lib/bootstrap/scss/mixins/_grid.scss | 69 +
.../lib/bootstrap/scss/mixins/_hover.scss | 37 +
.../lib/bootstrap/scss/mixins/_image.scss | 36 +
.../bootstrap/scss/mixins/_list-group.scss | 21 +
.../lib/bootstrap/scss/mixins/_lists.scss | 7 +
.../bootstrap/scss/mixins/_nav-divider.scss | 11 +
.../bootstrap/scss/mixins/_pagination.scss | 22 +
.../bootstrap/scss/mixins/_reset-text.scss | 17 +
.../lib/bootstrap/scss/mixins/_resize.scss | 6 +
.../bootstrap/scss/mixins/_screen-reader.scss | 34 +
.../lib/bootstrap/scss/mixins/_size.scss | 7 +
.../lib/bootstrap/scss/mixins/_table-row.scss | 39 +
.../bootstrap/scss/mixins/_text-emphasis.scss | 17 +
.../lib/bootstrap/scss/mixins/_text-hide.scss | 11 +
.../bootstrap/scss/mixins/_text-truncate.scss | 8 +
.../bootstrap/scss/mixins/_transition.scss | 16 +
.../bootstrap/scss/mixins/_visibility.scss | 8 +
.../lib/bootstrap/scss/utilities/_align.scss | 8 +
.../bootstrap/scss/utilities/_background.scss | 19 +
.../bootstrap/scss/utilities/_borders.scss | 75 ++
.../bootstrap/scss/utilities/_clearfix.scss | 3 +
.../bootstrap/scss/utilities/_display.scss | 26 +
.../lib/bootstrap/scss/utilities/_embed.scss | 39 +
.../lib/bootstrap/scss/utilities/_flex.scss | 51 +
.../lib/bootstrap/scss/utilities/_float.scss | 11 +
.../bootstrap/scss/utilities/_overflow.scss | 5 +
.../bootstrap/scss/utilities/_position.scss | 32 +
.../scss/utilities/_screenreaders.scss | 11 +
.../bootstrap/scss/utilities/_shadows.scss | 6 +
.../lib/bootstrap/scss/utilities/_sizing.scss | 20 +
.../bootstrap/scss/utilities/_spacing.scss | 73 ++
.../scss/utilities/_stretched-link.scss | 19 +
.../lib/bootstrap/scss/utilities/_text.scss | 72 ++
.../bootstrap/scss/utilities/_visibility.scss | 13 +
.../lib/bootstrap/scss/vendor/_rfs.scss | 204 +++
.../wwwroot/lib/jquery/LICENSE.txt | 20 +
.../wwwroot/lib/jquery/README.md | 62 +
.../OidcSamples/OidcSamples.TaxApp/Startup.cs | 10 +-
.../OidcSamples.TrafficPoliceApi/Startup.cs | 2 +-
CSharp/OidcSamples/OidcSamples.sln | 8 +-
React/{ => traffic-police}/.gitignore | 0
React/{ => traffic-police}/README.md | 0
React/{ => traffic-police}/cert.pem | 0
React/{ => traffic-police}/key.pem | 0
React/{ => traffic-police}/keytmp.pem | 0
React/{ => traffic-police}/package-lock.json | 0
React/{ => traffic-police}/package.json | 0
React/{ => traffic-police}/public/favicon.ico | Bin
React/{ => traffic-police}/public/index.html | 0
React/{ => traffic-police}/src/App.js | 14 +-
.../src/actions/authActions.js | 0
.../{ => traffic-police}/src/actions/types.js | 0
.../{ => traffic-police}/src/images/logo.svg | 0
React/{ => traffic-police}/src/index.css | 0
React/{ => traffic-police}/src/index.js | 0
.../src/pages/RegisterVehicle.js | 0
.../src/pages/YourVehicles.js | 0
.../src/pages/components/Button.js | 0
.../src/pages/components/Card.js | 0
.../src/pages/components/Heading1.js | 0
.../src/pages/components/Heading3.js | 0
.../src/pages/components/LoginWindow.js | 0
.../src/pages/components/Main.js | 0
.../src/pages/components/Navbar.js | 0
.../pages/components/VehicleRegisterForm.js | 0
.../src/pages/components/VehiclesMain.js | 0
.../pages/components/inputs/InputSelect.js | 0
.../src/pages/components/inputs/InputText.js | 0
.../src/pages/components/table/ColumnName.js | 0
.../src/pages/components/table/EditBtns.js | 0
.../src/pages/components/table/Table.js | 0
.../src/pages/components/table/TableData.js | 0
React/{ => traffic-police}/src/pages/home.js | 0
React/{ => traffic-police}/src/pages/login.js | 0
.../src/pages/signin-oidc.js | 0
.../src/pages/signout-oidc.js | 0
.../src/reducers/authReducer.js | 0
.../src/reducers/index.js | 0
.../src/services/apiService.js | 0
.../src/services/userService.js | 4 +-
React/{ => traffic-police}/src/store.js | 0
.../src/utils/authProvider.js | 0
.../src/utils/axiosHeaders.js | 0
.../src/utils/protectedRoute.js | 0
React/{ => traffic-police}/yarn.lock | 0
201 files changed, 10992 insertions(+), 16 deletions(-)
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Login.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Consent/Index.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/Success.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeCapture.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeConfirmation.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Diagnostics/Index.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Grants/Index.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Error.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Redirect.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Layout.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Nav.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ScopeListItem.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ValidationSummary.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewImports.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewStart.cshtml
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/tempkey.jwk
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.css
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.min.css
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/favicon.ico
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.jpg
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.png
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/js/signin-redirect.js
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/js/signout-redirect.js
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/README.md
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_button-group.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_buttons.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_card.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_carousel.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_close.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_code.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_custom-forms.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_dropdown.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_forms.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_functions.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_grid.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_images.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_input-group.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_jumbotron.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_list-group.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_media.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_mixins.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_modal.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_nav.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_navbar.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_pagination.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_popover.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_print.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_progress.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_reboot.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_root.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_spinners.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_tables.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_toasts.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_tooltip.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_transitions.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_type.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_utilities.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_variables.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/bootstrap-grid.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/bootstrap-reboot.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/bootstrap.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_alert.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_background-variant.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_badge.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_border-radius.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_box-shadow.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_breakpoints.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_buttons.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_caret.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_clearfix.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_deprecate.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_float.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_forms.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_gradients.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_grid-framework.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_grid.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_hover.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_image.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_list-group.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_lists.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_nav-divider.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_pagination.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_reset-text.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_resize.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_screen-reader.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_size.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_table-row.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_text-emphasis.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_text-hide.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_text-truncate.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_transition.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_visibility.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_align.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_background.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_borders.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_clearfix.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_display.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_embed.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_flex.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_float.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_overflow.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_position.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_screenreaders.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_shadows.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_sizing.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_spacing.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_stretched-link.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_text.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_visibility.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/vendor/_rfs.scss
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/jquery/LICENSE.txt
create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/jquery/README.md
rename React/{ => traffic-police}/.gitignore (100%)
rename React/{ => traffic-police}/README.md (100%)
rename React/{ => traffic-police}/cert.pem (100%)
rename React/{ => traffic-police}/key.pem (100%)
rename React/{ => traffic-police}/keytmp.pem (100%)
rename React/{ => traffic-police}/package-lock.json (100%)
rename React/{ => traffic-police}/package.json (100%)
rename React/{ => traffic-police}/public/favicon.ico (100%)
rename React/{ => traffic-police}/public/index.html (100%)
rename React/{ => traffic-police}/src/App.js (85%)
rename React/{ => traffic-police}/src/actions/authActions.js (100%)
rename React/{ => traffic-police}/src/actions/types.js (100%)
rename React/{ => traffic-police}/src/images/logo.svg (100%)
rename React/{ => traffic-police}/src/index.css (100%)
rename React/{ => traffic-police}/src/index.js (100%)
rename React/{ => traffic-police}/src/pages/RegisterVehicle.js (100%)
rename React/{ => traffic-police}/src/pages/YourVehicles.js (100%)
rename React/{ => traffic-police}/src/pages/components/Button.js (100%)
rename React/{ => traffic-police}/src/pages/components/Card.js (100%)
rename React/{ => traffic-police}/src/pages/components/Heading1.js (100%)
rename React/{ => traffic-police}/src/pages/components/Heading3.js (100%)
rename React/{ => traffic-police}/src/pages/components/LoginWindow.js (100%)
rename React/{ => traffic-police}/src/pages/components/Main.js (100%)
rename React/{ => traffic-police}/src/pages/components/Navbar.js (100%)
rename React/{ => traffic-police}/src/pages/components/VehicleRegisterForm.js (100%)
rename React/{ => traffic-police}/src/pages/components/VehiclesMain.js (100%)
rename React/{ => traffic-police}/src/pages/components/inputs/InputSelect.js (100%)
rename React/{ => traffic-police}/src/pages/components/inputs/InputText.js (100%)
rename React/{ => traffic-police}/src/pages/components/table/ColumnName.js (100%)
rename React/{ => traffic-police}/src/pages/components/table/EditBtns.js (100%)
rename React/{ => traffic-police}/src/pages/components/table/Table.js (100%)
rename React/{ => traffic-police}/src/pages/components/table/TableData.js (100%)
rename React/{ => traffic-police}/src/pages/home.js (100%)
rename React/{ => traffic-police}/src/pages/login.js (100%)
rename React/{ => traffic-police}/src/pages/signin-oidc.js (100%)
rename React/{ => traffic-police}/src/pages/signout-oidc.js (100%)
rename React/{ => traffic-police}/src/reducers/authReducer.js (100%)
rename React/{ => traffic-police}/src/reducers/index.js (100%)
rename React/{ => traffic-police}/src/services/apiService.js (100%)
rename React/{ => traffic-police}/src/services/userService.js (90%)
rename React/{ => traffic-police}/src/store.js (100%)
rename React/{ => traffic-police}/src/utils/authProvider.js (100%)
rename React/{ => traffic-police}/src/utils/axiosHeaders.js (100%)
rename React/{ => traffic-police}/src/utils/protectedRoute.js (100%)
rename React/{ => traffic-police}/yarn.lock (100%)
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs
new file mode 100644
index 0000000..81c38e2
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityModel;
+using IdentityServer4;
+using IdentityServer4.Models;
+using IdentityServer4.Stores;
+using System.Collections.Generic;
+
+namespace OidcSamples.AuthorizationServer
+{
+ public static class Config
+ {
+ public static IEnumerable IdentityResources =>
+ new IdentityResource[]
+ {
+ new IdentityResources.OpenId(),
+ new IdentityResources.Profile(),
+ new IdentityResources.Email(),
+ new IdentityResources.Address(),
+ };
+
+ private const string TrafficPoliceApi = "traffic-police-api";
+
+ public static IEnumerable ApiScopes =>
+ new ApiScope[]
+ {
+ new ApiScope(
+ TrafficPoliceApi,
+ "Traffic Police API scope"),
+ };
+
+ public static IEnumerable ApiResources =>
+ new ApiResource[] {
+ new ApiResource(TrafficPoliceApi, "Traffic Police API")
+ {
+ // This will make sure that `traffic-police-api` will be in the
+ // list of audiences when this scope is requested
+ Scopes = new List{ TrafficPoliceApi },
+ },
+ };
+
+ public static IEnumerable Clients =>
+ new Client[]
+ {
+ new Client
+ {
+ // IdentityTokenLifetime =
+ // AuthorizationCodeLifetime =
+ AccessTokenLifetime = 60 * 60 * 8,
+ AllowOfflineAccess = true,
+ UpdateAccessTokenClaimsOnRefresh = true,
+ ClientName = "Traffic Police React App",
+ ClientId = "traffic-police-react-app",
+ AllowedGrantTypes = GrantTypes.Code,
+ RedirectUris =
+ {
+ "https://localhost:3000/signin-oidc"
+ },
+ AllowedScopes =
+ {
+ IdentityServerConstants.StandardScopes.OpenId,
+ IdentityServerConstants.StandardScopes.Profile,
+ IdentityServerConstants.StandardScopes.Email,
+ IdentityServerConstants.StandardScopes.Address,
+ "traffic-police-api",
+ },
+ RequirePkce = true,
+ PostLogoutRedirectUris =
+ {
+ "https://localhost:3000/signout-callback-oidc"
+ },
+
+ RequireConsent = false,
+ },
+ new Client
+ {
+ AccessTokenLifetime = 60 * 60 * 8,
+ AllowOfflineAccess = true,
+ UpdateAccessTokenClaimsOnRefresh = true,
+ ClientName = "Tax ASP.NET Core Server Side App",
+ ClientId = "tax-asp-net-core-app",
+ AllowedGrantTypes = GrantTypes.Code,
+ RedirectUris =
+ {
+ "https://localhost:7001/signin-oidc"
+ },
+ AllowedScopes =
+ {
+ IdentityServerConstants.StandardScopes.OpenId,
+ IdentityServerConstants.StandardScopes.Profile,
+ IdentityServerConstants.StandardScopes.Address,
+ IdentityServerConstants.StandardScopes.Email,
+ "traffic-police-api",
+ },
+ ClientSecrets =
+ {
+ new Secret("secret".Sha256())
+ },
+ RequirePkce = true,
+ PostLogoutRedirectUris =
+ {
+ "https://localhost:7001/signout-callback-oidc"
+ },
+
+ RequireConsent = false,
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj
new file mode 100644
index 0000000..19aade1
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs
new file mode 100644
index 0000000..eeb934c
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using Serilog.Events;
+using Serilog.Sinks.SystemConsole.Themes;
+using System;
+
+namespace OidcSamples.AuthorizationServer
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
+ .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
+ .MinimumLevel.Override("System", LogEventLevel.Warning)
+ .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
+ .Enrich.FromLogContext()
+ .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code)
+ .CreateLogger();
+
+ try
+ {
+ Log.Information("Starting host...");
+ CreateHostBuilder(args).Build().Run();
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Host terminated unexpectedly.");
+ return 1;
+ }
+ finally
+ {
+ Log.CloseAndFlush();
+ }
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .UseSerilog()
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json
new file mode 100644
index 0000000..59dcdbe
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "SelfHost": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:10000"
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs
new file mode 100644
index 0000000..2d51c3c
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs
@@ -0,0 +1,368 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityModel;
+using IdentityServer4;
+using IdentityServer4.Events;
+using IdentityServer4.Extensions;
+using IdentityServer4.Models;
+using IdentityServer4.Services;
+using IdentityServer4.Stores;
+using IdentityServer4.Test;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ ///
+ /// This sample controller implements a typical login/logout/provision workflow for local and external accounts.
+ /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production!
+ /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval
+ ///
+ [SecurityHeaders]
+ [AllowAnonymous]
+ public class AccountController : Controller
+ {
+ private readonly TestUserStore _users;
+ private readonly IIdentityServerInteractionService _interaction;
+ private readonly IClientStore _clientStore;
+ private readonly IAuthenticationSchemeProvider _schemeProvider;
+ private readonly IEventService _events;
+
+ public AccountController(
+ IIdentityServerInteractionService interaction,
+ IClientStore clientStore,
+ IAuthenticationSchemeProvider schemeProvider,
+ IEventService events,
+ TestUserStore users = null)
+ {
+ // if the TestUserStore is not in DI, then we'll just use the global users collection
+ // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
+ _users = users ?? new TestUserStore(TestUsers.Users);
+
+ _interaction = interaction;
+ _clientStore = clientStore;
+ _schemeProvider = schemeProvider;
+ _events = events;
+ }
+
+ ///
+ /// Entry point into the login workflow
+ ///
+ [HttpGet]
+ public async Task Login(string returnUrl)
+ {
+ // build a model so we know what to show on the login page
+ var vm = await BuildLoginViewModelAsync(returnUrl);
+
+ if (vm.IsExternalLoginOnly)
+ {
+ // we only have one option for logging in and it's an external provider
+ return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl });
+ }
+
+ return View(vm);
+ }
+
+ ///
+ /// Handle postback from username/password login
+ ///
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Login(LoginInputModel model, string button)
+ {
+ // check if we are in the context of an authorization request
+ var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
+
+ // the user clicked the "cancel" button
+ if (button != "login")
+ {
+ if (context != null)
+ {
+ // if the user cancels, send a result back into IdentityServer as if they
+ // denied the consent (even if this client does not require consent).
+ // this will send back an access denied OIDC error response to the client.
+ await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
+
+ // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
+ if (context.IsNativeClient())
+ {
+ // The client is native, so this change in how to
+ // return the response is for better UX for the end user.
+ return this.LoadingPage("Redirect", model.ReturnUrl);
+ }
+
+ return Redirect(model.ReturnUrl);
+ }
+ else
+ {
+ // since we don't have a valid context, then we just go back to the home page
+ return Redirect("~/");
+ }
+ }
+
+ if (ModelState.IsValid)
+ {
+ // validate username/password against in-memory store
+ if (_users.ValidateCredentials(model.Username, model.Password))
+ {
+ var user = _users.FindByUsername(model.Username);
+ await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId));
+
+ // only set explicit expiration here if user chooses "remember me".
+ // otherwise we rely upon expiration configured in cookie middleware.
+ AuthenticationProperties props = null;
+ if (AccountOptions.AllowRememberLogin && model.RememberLogin)
+ {
+ props = new AuthenticationProperties
+ {
+ IsPersistent = true,
+ ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
+ };
+ };
+
+ // issue authentication cookie with subject ID and username
+ var isuser = new IdentityServerUser(user.SubjectId)
+ {
+ DisplayName = user.Username
+ };
+
+ await HttpContext.SignInAsync(isuser, props);
+
+ if (context != null)
+ {
+ if (context.IsNativeClient())
+ {
+ // The client is native, so this change in how to
+ // return the response is for better UX for the end user.
+ return this.LoadingPage("Redirect", model.ReturnUrl);
+ }
+
+ // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
+ return Redirect(model.ReturnUrl);
+ }
+
+ // request for a local page
+ if (Url.IsLocalUrl(model.ReturnUrl))
+ {
+ return Redirect(model.ReturnUrl);
+ }
+ else if (string.IsNullOrEmpty(model.ReturnUrl))
+ {
+ return Redirect("~/");
+ }
+ else
+ {
+ // user might have clicked on a malicious link - should be logged
+ throw new Exception("invalid return URL");
+ }
+ }
+
+ await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId));
+ ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
+ }
+
+ // something went wrong, show form with error
+ var vm = await BuildLoginViewModelAsync(model);
+ return View(vm);
+ }
+
+
+ ///
+ /// Show logout page
+ ///
+ [HttpGet]
+ public async Task Logout(string logoutId)
+ {
+ // build a model so the logout page knows what to display
+ var vm = await BuildLogoutViewModelAsync(logoutId);
+
+ if (vm.ShowLogoutPrompt == false)
+ {
+ // if the request for logout was properly authenticated from IdentityServer, then
+ // we don't need to show the prompt and can just log the user out directly.
+ return await Logout(vm);
+ }
+
+ return View(vm);
+ }
+
+ ///
+ /// Handle logout page postback
+ ///
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Logout(LogoutInputModel model)
+ {
+ // build a model so the logged out page knows what to display
+ var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
+
+ if (User?.Identity.IsAuthenticated == true)
+ {
+ // delete local authentication cookie
+ await HttpContext.SignOutAsync();
+
+ // raise the logout event
+ await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
+ }
+
+ // check if we need to trigger sign-out at an upstream identity provider
+ if (vm.TriggerExternalSignout)
+ {
+ // build a return URL so the upstream provider will redirect back
+ // to us after the user has logged out. this allows us to then
+ // complete our single sign-out processing.
+ string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
+
+ // this triggers a redirect to the external provider for sign-out
+ return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
+ }
+
+ return View("LoggedOut", vm);
+ }
+
+ [HttpGet]
+ public IActionResult AccessDenied()
+ {
+ return View();
+ }
+
+
+ /*****************************************/
+ /* helper APIs for the AccountController */
+ /*****************************************/
+ private async Task BuildLoginViewModelAsync(string returnUrl)
+ {
+ var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
+ if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
+ {
+ var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider;
+
+ // this is meant to short circuit the UI and only trigger the one external IdP
+ var vm = new LoginViewModel
+ {
+ EnableLocalLogin = local,
+ ReturnUrl = returnUrl,
+ Username = context?.LoginHint,
+ };
+
+ if (!local)
+ {
+ vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } };
+ }
+
+ return vm;
+ }
+
+ var schemes = await _schemeProvider.GetAllSchemesAsync();
+
+ var providers = schemes
+ .Where(x => x.DisplayName != null)
+ .Select(x => new ExternalProvider
+ {
+ DisplayName = x.DisplayName ?? x.Name,
+ AuthenticationScheme = x.Name
+ }).ToList();
+
+ var allowLocal = true;
+ if (context?.Client.ClientId != null)
+ {
+ var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId);
+ if (client != null)
+ {
+ allowLocal = client.EnableLocalLogin;
+
+ if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
+ {
+ providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
+ }
+ }
+ }
+
+ return new LoginViewModel
+ {
+ AllowRememberLogin = AccountOptions.AllowRememberLogin,
+ EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
+ ReturnUrl = returnUrl,
+ Username = context?.LoginHint,
+ ExternalProviders = providers.ToArray()
+ };
+ }
+
+ private async Task BuildLoginViewModelAsync(LoginInputModel model)
+ {
+ var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
+ vm.Username = model.Username;
+ vm.RememberLogin = model.RememberLogin;
+ return vm;
+ }
+
+ private async Task BuildLogoutViewModelAsync(string logoutId)
+ {
+ var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };
+
+ if (User?.Identity.IsAuthenticated != true)
+ {
+ // if the user is not authenticated, then just show logged out page
+ vm.ShowLogoutPrompt = false;
+ return vm;
+ }
+
+ var context = await _interaction.GetLogoutContextAsync(logoutId);
+ if (context?.ShowSignoutPrompt == false)
+ {
+ // it's safe to automatically sign-out
+ vm.ShowLogoutPrompt = false;
+ return vm;
+ }
+
+ // show the logout prompt. this prevents attacks where the user
+ // is automatically signed out by another malicious web page.
+ return vm;
+ }
+
+ private async Task BuildLoggedOutViewModelAsync(string logoutId)
+ {
+ // get context information (client name, post logout redirect URI and iframe for federated signout)
+ var logout = await _interaction.GetLogoutContextAsync(logoutId);
+
+ var vm = new LoggedOutViewModel
+ {
+ AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
+ PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
+ ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
+ SignOutIframeUrl = logout?.SignOutIFrameUrl,
+ LogoutId = logoutId
+ };
+
+ if (User?.Identity.IsAuthenticated == true)
+ {
+ var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
+ if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
+ {
+ var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
+ if (providerSupportsSignout)
+ {
+ if (vm.LogoutId == null)
+ {
+ // if there's no current logout context, we need to create one
+ // this captures necessary info from the current logged in user
+ // before we signout and redirect away to the external IdP for signout
+ vm.LogoutId = await _interaction.CreateLogoutContextAsync();
+ }
+
+ vm.ExternalAuthenticationScheme = idp;
+ }
+ }
+ }
+
+ return vm;
+ }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs
new file mode 100644
index 0000000..a89c229
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class AccountOptions
+ {
+ public static bool AllowLocalLogin = true;
+ public static bool AllowRememberLogin = true;
+ public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30);
+
+ public static bool ShowLogoutPrompt = true;
+ public static bool AutomaticRedirectAfterSignOut = true;
+
+ public static string InvalidCredentialsErrorMessage = "Invalid username or password";
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs
new file mode 100644
index 0000000..1a7479e
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs
@@ -0,0 +1,196 @@
+using IdentityModel;
+using IdentityServer4;
+using IdentityServer4.Events;
+using IdentityServer4.Services;
+using IdentityServer4.Stores;
+using IdentityServer4.Test;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ [SecurityHeaders]
+ [AllowAnonymous]
+ public class ExternalController : Controller
+ {
+ private readonly TestUserStore _users;
+ private readonly IIdentityServerInteractionService _interaction;
+ private readonly IClientStore _clientStore;
+ private readonly ILogger _logger;
+ private readonly IEventService _events;
+
+ public ExternalController(
+ IIdentityServerInteractionService interaction,
+ IClientStore clientStore,
+ IEventService events,
+ ILogger logger,
+ TestUserStore users = null)
+ {
+ // if the TestUserStore is not in DI, then we'll just use the global users collection
+ // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
+ _users = users ?? new TestUserStore(TestUsers.Users);
+
+ _interaction = interaction;
+ _clientStore = clientStore;
+ _logger = logger;
+ _events = events;
+ }
+
+ ///
+ /// initiate roundtrip to external authentication provider
+ ///
+ [HttpGet]
+ public IActionResult Challenge(string scheme, string returnUrl)
+ {
+ if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";
+
+ // validate returnUrl - either it is a valid OIDC URL or back to a local page
+ if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
+ {
+ // user might have clicked on a malicious link - should be logged
+ throw new Exception("invalid return URL");
+ }
+
+ // start challenge and roundtrip the return URL and scheme
+ var props = new AuthenticationProperties
+ {
+ RedirectUri = Url.Action(nameof(Callback)),
+ Items =
+ {
+ { "returnUrl", returnUrl },
+ { "scheme", scheme },
+ }
+ };
+
+ return Challenge(props, scheme);
+
+ }
+
+ ///
+ /// Post processing of external authentication
+ ///
+ [HttpGet]
+ public async Task Callback()
+ {
+ // read external identity from the temporary cookie
+ var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
+ if (result?.Succeeded != true)
+ {
+ throw new Exception("External authentication error");
+ }
+
+ if (_logger.IsEnabled(LogLevel.Debug))
+ {
+ var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
+ _logger.LogDebug("External claims: {@claims}", externalClaims);
+ }
+
+ // lookup our user and external provider info
+ var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
+ if (user == null)
+ {
+ // this might be where you might initiate a custom workflow for user registration
+ // in this sample we don't show how that would be done, as our sample implementation
+ // simply auto-provisions new external user
+ user = AutoProvisionUser(provider, providerUserId, claims);
+ }
+
+ // this allows us to collect any additional claims or properties
+ // for the specific protocols used and store them in the local auth cookie.
+ // this is typically used to store data needed for signout from those protocols.
+ var additionalLocalClaims = new List();
+ var localSignInProps = new AuthenticationProperties();
+ ProcessLoginCallback(result, additionalLocalClaims, localSignInProps);
+
+ // issue authentication cookie for user
+ var isuser = new IdentityServerUser(user.SubjectId)
+ {
+ DisplayName = user.Username,
+ IdentityProvider = provider,
+ AdditionalClaims = additionalLocalClaims
+ };
+
+ await HttpContext.SignInAsync(isuser, localSignInProps);
+
+ // delete temporary cookie used during external authentication
+ await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
+
+ // retrieve return URL
+ var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
+
+ // check if external login is in the context of an OIDC request
+ var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
+ await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId));
+
+ if (context != null)
+ {
+ if (context.IsNativeClient())
+ {
+ // The client is native, so this change in how to
+ // return the response is for better UX for the end user.
+ return this.LoadingPage("Redirect", returnUrl);
+ }
+ }
+
+ return Redirect(returnUrl);
+ }
+
+ private (TestUser user, string provider, string providerUserId, IEnumerable claims) FindUserFromExternalProvider(AuthenticateResult result)
+ {
+ var externalUser = result.Principal;
+
+ // try to determine the unique id of the external user (issued by the provider)
+ // the most common claim type for that are the sub claim and the NameIdentifier
+ // depending on the external provider, some other claim type might be used
+ var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
+ externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
+ throw new Exception("Unknown userid");
+
+ // remove the user id claim so we don't include it as an extra claim if/when we provision the user
+ var claims = externalUser.Claims.ToList();
+ claims.Remove(userIdClaim);
+
+ var provider = result.Properties.Items["scheme"];
+ var providerUserId = userIdClaim.Value;
+
+ // find external user
+ var user = _users.FindByExternalProvider(provider, providerUserId);
+
+ return (user, provider, providerUserId, claims);
+ }
+
+ private TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable claims)
+ {
+ var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList());
+ return user;
+ }
+
+ // if the external login is OIDC-based, there are certain things we need to preserve to make logout work
+ // this will be different for WS-Fed, SAML2p or other protocols
+ private void ProcessLoginCallback(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps)
+ {
+ // if the external system sent a session id claim, copy it over
+ // so we can use it for single sign-out
+ var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
+ if (sid != null)
+ {
+ localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
+ }
+
+ // if the external provider issued an id_token, we'll keep it for signout
+ var idToken = externalResult.Properties.GetTokenValue("id_token");
+ if (idToken != null)
+ {
+ localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs
new file mode 100644
index 0000000..0584aa6
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class ExternalProvider
+ {
+ public string DisplayName { get; set; }
+ public string AuthenticationScheme { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs
new file mode 100644
index 0000000..6368832
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class LoggedOutViewModel
+ {
+ public string PostLogoutRedirectUri { get; set; }
+ public string ClientName { get; set; }
+ public string SignOutIframeUrl { get; set; }
+
+ public bool AutomaticRedirectAfterSignOut { get; set; }
+
+ public string LogoutId { get; set; }
+ public bool TriggerExternalSignout => ExternalAuthenticationScheme != null;
+ public string ExternalAuthenticationScheme { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs
new file mode 100644
index 0000000..bcb7853
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System.ComponentModel.DataAnnotations;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class LoginInputModel
+ {
+ [Required]
+ public string Username { get; set; }
+ [Required]
+ public string Password { get; set; }
+ public bool RememberLogin { get; set; }
+ public string ReturnUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs
new file mode 100644
index 0000000..9bf6c8f
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class LoginViewModel : LoginInputModel
+ {
+ public bool AllowRememberLogin { get; set; } = true;
+ public bool EnableLocalLogin { get; set; } = true;
+
+ public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty();
+ public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName));
+
+ public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1;
+ public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null;
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs
new file mode 100644
index 0000000..300b5f0
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class LogoutInputModel
+ {
+ public string LogoutId { get; set; }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs
new file mode 100644
index 0000000..236cd6c
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class LogoutViewModel : LogoutInputModel
+ {
+ public bool ShowLogoutPrompt { get; set; } = true;
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs
new file mode 100644
index 0000000..904ae20
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class RedirectViewModel
+ {
+ public string RedirectUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs
new file mode 100644
index 0000000..6c3aa44
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs
@@ -0,0 +1,262 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServer4.Events;
+using IdentityServer4.Models;
+using IdentityServer4.Services;
+using IdentityServer4.Extensions;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+using System.Threading.Tasks;
+using IdentityServer4.Validation;
+using System.Collections.Generic;
+using System;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ ///
+ /// This controller processes the consent UI
+ ///
+ [SecurityHeaders]
+ [Authorize]
+ public class ConsentController : Controller
+ {
+ private readonly IIdentityServerInteractionService _interaction;
+ private readonly IEventService _events;
+ private readonly ILogger _logger;
+
+ public ConsentController(
+ IIdentityServerInteractionService interaction,
+ IEventService events,
+ ILogger logger)
+ {
+ _interaction = interaction;
+ _events = events;
+ _logger = logger;
+ }
+
+ ///
+ /// Shows the consent screen
+ ///
+ ///
+ ///
+ [HttpGet]
+ public async Task Index(string returnUrl)
+ {
+ var vm = await BuildViewModelAsync(returnUrl);
+ if (vm != null)
+ {
+ return View("Index", vm);
+ }
+
+ return View("Error");
+ }
+
+ ///
+ /// Handles the consent screen postback
+ ///
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Index(ConsentInputModel model)
+ {
+ var result = await ProcessConsent(model);
+
+ if (result.IsRedirect)
+ {
+ var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
+ if (context?.IsNativeClient() == true)
+ {
+ // The client is native, so this change in how to
+ // return the response is for better UX for the end user.
+ return this.LoadingPage("Redirect", result.RedirectUri);
+ }
+
+ return Redirect(result.RedirectUri);
+ }
+
+ if (result.HasValidationError)
+ {
+ ModelState.AddModelError(string.Empty, result.ValidationError);
+ }
+
+ if (result.ShowView)
+ {
+ return View("Index", result.ViewModel);
+ }
+
+ return View("Error");
+ }
+
+ /*****************************************/
+ /* helper APIs for the ConsentController */
+ /*****************************************/
+ private async Task ProcessConsent(ConsentInputModel model)
+ {
+ var result = new ProcessConsentResult();
+
+ // validate return url is still valid
+ var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
+ if (request == null) return result;
+
+ ConsentResponse grantedConsent = null;
+
+ // user clicked 'no' - send back the standard 'access_denied' response
+ if (model?.Button == "no")
+ {
+ grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };
+
+ // emit event
+ await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
+ }
+ // user clicked 'yes' - validate the data
+ else if (model?.Button == "yes")
+ {
+ // if the user consented to some scope, build the response model
+ if (model.ScopesConsented != null && model.ScopesConsented.Any())
+ {
+ var scopes = model.ScopesConsented;
+ if (ConsentOptions.EnableOfflineAccess == false)
+ {
+ scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess);
+ }
+
+ grantedConsent = new ConsentResponse
+ {
+ RememberConsent = model.RememberConsent,
+ ScopesValuesConsented = scopes.ToArray(),
+ Description = model.Description
+ };
+
+ // emit event
+ await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
+ }
+ else
+ {
+ result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
+ }
+ }
+ else
+ {
+ result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
+ }
+
+ if (grantedConsent != null)
+ {
+ // communicate outcome of consent back to identityserver
+ await _interaction.GrantConsentAsync(request, grantedConsent);
+
+ // indicate that's it ok to redirect back to authorization endpoint
+ result.RedirectUri = model.ReturnUrl;
+ result.Client = request.Client;
+ }
+ else
+ {
+ // we need to redisplay the consent UI
+ result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model);
+ }
+
+ return result;
+ }
+
+ private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null)
+ {
+ var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
+ if (request != null)
+ {
+ return CreateConsentViewModel(model, returnUrl, request);
+ }
+ else
+ {
+ _logger.LogError("No consent request matching request: {0}", returnUrl);
+ }
+
+ return null;
+ }
+
+ private ConsentViewModel CreateConsentViewModel(
+ ConsentInputModel model, string returnUrl,
+ AuthorizationRequest request)
+ {
+ var vm = new ConsentViewModel
+ {
+ RememberConsent = model?.RememberConsent ?? true,
+ ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(),
+ Description = model?.Description,
+
+ ReturnUrl = returnUrl,
+
+ ClientName = request.Client.ClientName ?? request.Client.ClientId,
+ ClientUrl = request.Client.ClientUri,
+ ClientLogoUrl = request.Client.LogoUri,
+ AllowRememberConsent = request.Client.AllowRememberConsent
+ };
+
+ vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
+
+ var apiScopes = new List();
+ foreach(var parsedScope in request.ValidatedResources.ParsedScopes)
+ {
+ var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName);
+ if (apiScope != null)
+ {
+ var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null);
+ apiScopes.Add(scopeVm);
+ }
+ }
+ if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess)
+ {
+ apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null));
+ }
+ vm.ApiScopes = apiScopes;
+
+ return vm;
+ }
+
+ private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check)
+ {
+ return new ScopeViewModel
+ {
+ Value = identity.Name,
+ DisplayName = identity.DisplayName ?? identity.Name,
+ Description = identity.Description,
+ Emphasize = identity.Emphasize,
+ Required = identity.Required,
+ Checked = check || identity.Required
+ };
+ }
+
+ public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check)
+ {
+ var displayName = apiScope.DisplayName ?? apiScope.Name;
+ if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter))
+ {
+ displayName += ":" + parsedScopeValue.ParsedParameter;
+ }
+
+ return new ScopeViewModel
+ {
+ Value = parsedScopeValue.RawValue,
+ DisplayName = displayName,
+ Description = apiScope.Description,
+ Emphasize = apiScope.Emphasize,
+ Required = apiScope.Required,
+ Checked = check || apiScope.Required
+ };
+ }
+
+ private ScopeViewModel GetOfflineAccessScope(bool check)
+ {
+ return new ScopeViewModel
+ {
+ Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess,
+ DisplayName = ConsentOptions.OfflineAccessDisplayName,
+ Description = ConsentOptions.OfflineAccessDescription,
+ Emphasize = true,
+ Checked = check
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs
new file mode 100644
index 0000000..ee2d5eb
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System.Collections.Generic;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class ConsentInputModel
+ {
+ public string Button { get; set; }
+ public IEnumerable ScopesConsented { get; set; }
+ public bool RememberConsent { get; set; }
+ public string ReturnUrl { get; set; }
+ public string Description { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs
new file mode 100644
index 0000000..3635d0e
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class ConsentOptions
+ {
+ public static bool EnableOfflineAccess = true;
+ public static string OfflineAccessDisplayName = "Offline Access";
+ public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline";
+
+ public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission";
+ public static readonly string InvalidSelectionErrorMessage = "Invalid selection";
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs
new file mode 100644
index 0000000..44c41bd
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System.Collections.Generic;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class ConsentViewModel : ConsentInputModel
+ {
+ public string ClientName { get; set; }
+ public string ClientUrl { get; set; }
+ public string ClientLogoUrl { get; set; }
+ public bool AllowRememberConsent { get; set; }
+
+ public IEnumerable IdentityScopes { get; set; }
+ public IEnumerable ApiScopes { get; set; }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs
new file mode 100644
index 0000000..1d331df
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServer4.Models;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class ProcessConsentResult
+ {
+ public bool IsRedirect => RedirectUri != null;
+ public string RedirectUri { get; set; }
+ public Client Client { get; set; }
+
+ public bool ShowView => ViewModel != null;
+ public ConsentViewModel ViewModel { get; set; }
+
+ public bool HasValidationError => ValidationError != null;
+ public string ValidationError { get; set; }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs
new file mode 100644
index 0000000..fa57988
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class ScopeViewModel
+ {
+ public string Value { get; set; }
+ public string DisplayName { get; set; }
+ public string Description { get; set; }
+ public bool Emphasize { get; set; }
+ public bool Required { get; set; }
+ public bool Checked { get; set; }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs
new file mode 100644
index 0000000..a221181
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class DeviceAuthorizationInputModel : ConsentInputModel
+ {
+ public string UserCode { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs
new file mode 100644
index 0000000..3e8857f
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class DeviceAuthorizationViewModel : ConsentViewModel
+ {
+ public string UserCode { get; set; }
+ public bool ConfirmUserCode { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs
new file mode 100644
index 0000000..d7d07ae
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs
@@ -0,0 +1,232 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using IdentityServer4.Configuration;
+using IdentityServer4.Events;
+using IdentityServer4.Extensions;
+using IdentityServer4.Models;
+using IdentityServer4.Services;
+using IdentityServer4.Validation;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ [Authorize]
+ [SecurityHeaders]
+ public class DeviceController : Controller
+ {
+ private readonly IDeviceFlowInteractionService _interaction;
+ private readonly IEventService _events;
+ private readonly IOptions _options;
+ private readonly ILogger _logger;
+
+ public DeviceController(
+ IDeviceFlowInteractionService interaction,
+ IEventService eventService,
+ IOptions options,
+ ILogger logger)
+ {
+ _interaction = interaction;
+ _events = eventService;
+ _options = options;
+ _logger = logger;
+ }
+
+ [HttpGet]
+ public async Task Index()
+ {
+ string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter;
+ string userCode = Request.Query[userCodeParamName];
+ if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture");
+
+ var vm = await BuildViewModelAsync(userCode);
+ if (vm == null) return View("Error");
+
+ vm.ConfirmUserCode = true;
+ return View("UserCodeConfirmation", vm);
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task UserCodeCapture(string userCode)
+ {
+ var vm = await BuildViewModelAsync(userCode);
+ if (vm == null) return View("Error");
+
+ return View("UserCodeConfirmation", vm);
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Callback(DeviceAuthorizationInputModel model)
+ {
+ if (model == null) throw new ArgumentNullException(nameof(model));
+
+ var result = await ProcessConsent(model);
+ if (result.HasValidationError) return View("Error");
+
+ return View("Success");
+ }
+
+ private async Task ProcessConsent(DeviceAuthorizationInputModel model)
+ {
+ var result = new ProcessConsentResult();
+
+ var request = await _interaction.GetAuthorizationContextAsync(model.UserCode);
+ if (request == null) return result;
+
+ ConsentResponse grantedConsent = null;
+
+ // user clicked 'no' - send back the standard 'access_denied' response
+ if (model.Button == "no")
+ {
+ grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };
+
+ // emit event
+ await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
+ }
+ // user clicked 'yes' - validate the data
+ else if (model.Button == "yes")
+ {
+ // if the user consented to some scope, build the response model
+ if (model.ScopesConsented != null && model.ScopesConsented.Any())
+ {
+ var scopes = model.ScopesConsented;
+ if (ConsentOptions.EnableOfflineAccess == false)
+ {
+ scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess);
+ }
+
+ grantedConsent = new ConsentResponse
+ {
+ RememberConsent = model.RememberConsent,
+ ScopesValuesConsented = scopes.ToArray(),
+ Description = model.Description
+ };
+
+ // emit event
+ await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
+ }
+ else
+ {
+ result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
+ }
+ }
+ else
+ {
+ result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
+ }
+
+ if (grantedConsent != null)
+ {
+ // communicate outcome of consent back to identityserver
+ await _interaction.HandleRequestAsync(model.UserCode, grantedConsent);
+
+ // indicate that's it ok to redirect back to authorization endpoint
+ result.RedirectUri = model.ReturnUrl;
+ result.Client = request.Client;
+ }
+ else
+ {
+ // we need to redisplay the consent UI
+ result.ViewModel = await BuildViewModelAsync(model.UserCode, model);
+ }
+
+ return result;
+ }
+
+ private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null)
+ {
+ var request = await _interaction.GetAuthorizationContextAsync(userCode);
+ if (request != null)
+ {
+ return CreateConsentViewModel(userCode, model, request);
+ }
+
+ return null;
+ }
+
+ private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request)
+ {
+ var vm = new DeviceAuthorizationViewModel
+ {
+ UserCode = userCode,
+ Description = model?.Description,
+
+ RememberConsent = model?.RememberConsent ?? true,
+ ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(),
+
+ ClientName = request.Client.ClientName ?? request.Client.ClientId,
+ ClientUrl = request.Client.ClientUri,
+ ClientLogoUrl = request.Client.LogoUri,
+ AllowRememberConsent = request.Client.AllowRememberConsent
+ };
+
+ vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
+
+ var apiScopes = new List();
+ foreach (var parsedScope in request.ValidatedResources.ParsedScopes)
+ {
+ var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName);
+ if (apiScope != null)
+ {
+ var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null);
+ apiScopes.Add(scopeVm);
+ }
+ }
+ if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess)
+ {
+ apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null));
+ }
+ vm.ApiScopes = apiScopes;
+
+ return vm;
+ }
+
+ private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check)
+ {
+ return new ScopeViewModel
+ {
+ Value = identity.Name,
+ DisplayName = identity.DisplayName ?? identity.Name,
+ Description = identity.Description,
+ Emphasize = identity.Emphasize,
+ Required = identity.Required,
+ Checked = check || identity.Required
+ };
+ }
+
+ public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check)
+ {
+ return new ScopeViewModel
+ {
+ Value = parsedScopeValue.RawValue,
+ // todo: use the parsed scope value in the display?
+ DisplayName = apiScope.DisplayName ?? apiScope.Name,
+ Description = apiScope.Description,
+ Emphasize = apiScope.Emphasize,
+ Required = apiScope.Required,
+ Checked = check || apiScope.Required
+ };
+ }
+ private ScopeViewModel GetOfflineAccessScope(bool check)
+ {
+ return new ScopeViewModel
+ {
+ Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess,
+ DisplayName = ConsentOptions.OfflineAccessDisplayName,
+ Description = ConsentOptions.OfflineAccessDescription,
+ Emphasize = true,
+ Checked = check
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs
new file mode 100644
index 0000000..57c2f55
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ [SecurityHeaders]
+ [Authorize]
+ public class DiagnosticsController : Controller
+ {
+ public async Task Index()
+ {
+ var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() };
+ if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString()))
+ {
+ return NotFound();
+ }
+
+ var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync());
+ return View(model);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs
new file mode 100644
index 0000000..f43c768
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityModel;
+using Microsoft.AspNetCore.Authentication;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class DiagnosticsViewModel
+ {
+ public DiagnosticsViewModel(AuthenticateResult result)
+ {
+ AuthenticateResult = result;
+
+ if (result.Properties.Items.ContainsKey("client_list"))
+ {
+ var encoded = result.Properties.Items["client_list"];
+ var bytes = Base64Url.Decode(encoded);
+ var value = Encoding.UTF8.GetString(bytes);
+
+ Clients = JsonConvert.DeserializeObject(value);
+ }
+ }
+
+ public AuthenticateResult AuthenticateResult { get; }
+ public IEnumerable Clients { get; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs
new file mode 100644
index 0000000..6c720b7
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs
@@ -0,0 +1,27 @@
+using System;
+using IdentityServer4.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public static class Extensions
+ {
+ ///
+ /// Checks if the redirect URI is for a native client.
+ ///
+ ///
+ public static bool IsNativeClient(this AuthorizationRequest context)
+ {
+ return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
+ && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
+ }
+
+ public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri)
+ {
+ controller.HttpContext.Response.StatusCode = 200;
+ controller.HttpContext.Response.Headers["Location"] = "";
+
+ return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri });
+ }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs
new file mode 100644
index 0000000..128ce59
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs
@@ -0,0 +1,97 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServer4.Services;
+using IdentityServer4.Stores;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using IdentityServer4.Events;
+using IdentityServer4.Extensions;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ ///
+ /// This sample controller allows a user to revoke grants given to clients
+ ///
+ [SecurityHeaders]
+ [Authorize]
+ public class GrantsController : Controller
+ {
+ private readonly IIdentityServerInteractionService _interaction;
+ private readonly IClientStore _clients;
+ private readonly IResourceStore _resources;
+ private readonly IEventService _events;
+
+ public GrantsController(IIdentityServerInteractionService interaction,
+ IClientStore clients,
+ IResourceStore resources,
+ IEventService events)
+ {
+ _interaction = interaction;
+ _clients = clients;
+ _resources = resources;
+ _events = events;
+ }
+
+ ///
+ /// Show list of grants
+ ///
+ [HttpGet]
+ public async Task Index()
+ {
+ return View("Index", await BuildViewModelAsync());
+ }
+
+ ///
+ /// Handle postback to revoke a client
+ ///
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Revoke(string clientId)
+ {
+ await _interaction.RevokeUserConsentAsync(clientId);
+ await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId));
+
+ return RedirectToAction("Index");
+ }
+
+ private async Task BuildViewModelAsync()
+ {
+ var grants = await _interaction.GetAllUserGrantsAsync();
+
+ var list = new List();
+ foreach(var grant in grants)
+ {
+ var client = await _clients.FindClientByIdAsync(grant.ClientId);
+ if (client != null)
+ {
+ var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes);
+
+ var item = new GrantViewModel()
+ {
+ ClientId = client.ClientId,
+ ClientName = client.ClientName ?? client.ClientId,
+ ClientLogoUrl = client.LogoUri,
+ ClientUrl = client.ClientUri,
+ Description = grant.Description,
+ Created = grant.CreationTime,
+ Expires = grant.Expiration,
+ IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(),
+ ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray()
+ };
+
+ list.Add(item);
+ }
+ }
+
+ return new GrantsViewModel
+ {
+ Grants = list
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs
new file mode 100644
index 0000000..f86354f
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using System;
+using System.Collections.Generic;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class GrantsViewModel
+ {
+ public IEnumerable Grants { get; set; }
+ }
+
+ public class GrantViewModel
+ {
+ public string ClientId { get; set; }
+ public string ClientName { get; set; }
+ public string ClientUrl { get; set; }
+ public string ClientLogoUrl { get; set; }
+ public string Description { get; set; }
+ public DateTime Created { get; set; }
+ public DateTime? Expires { get; set; }
+ public IEnumerable IdentityGrantNames { get; set; }
+ public IEnumerable ApiGrantNames { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs
new file mode 100644
index 0000000..7d2cbe3
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServer4.Models;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class ErrorViewModel
+ {
+ public ErrorViewModel()
+ {
+ }
+
+ public ErrorViewModel(string error)
+ {
+ Error = new ErrorMessage { Error = error };
+ }
+
+ public ErrorMessage Error { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs
new file mode 100644
index 0000000..9cf0678
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServer4.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ [SecurityHeaders]
+ [AllowAnonymous]
+ public class HomeController : Controller
+ {
+ private readonly IIdentityServerInteractionService _interaction;
+ private readonly IWebHostEnvironment _environment;
+ private readonly ILogger _logger;
+
+ public HomeController(IIdentityServerInteractionService interaction, IWebHostEnvironment environment, ILogger logger)
+ {
+ _interaction = interaction;
+ _environment = environment;
+ _logger = logger;
+ }
+
+ public IActionResult Index()
+ {
+ if (_environment.IsDevelopment())
+ {
+ // only show in development
+ return View();
+ }
+
+ _logger.LogInformation("Homepage is disabled in production. Returning 404.");
+ return NotFound();
+ }
+
+ ///
+ /// Shows the error page
+ ///
+ public async Task Error(string errorId)
+ {
+ var vm = new ErrorViewModel();
+
+ // retrieve error details from identityserver
+ var message = await _interaction.GetErrorContextAsync(errorId);
+ if (message != null)
+ {
+ vm.Error = message;
+
+ if (!_environment.IsDevelopment())
+ {
+ // only show in development
+ message.ErrorDescription = null;
+ }
+ }
+
+ return View("Error", vm);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs
new file mode 100644
index 0000000..382c340
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class SecurityHeadersAttribute : ActionFilterAttribute
+ {
+ public override void OnResultExecuting(ResultExecutingContext context)
+ {
+ var result = context.Result;
+ if (result is ViewResult)
+ {
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
+ if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options"))
+ {
+ context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff");
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options"))
+ {
+ context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+ var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';";
+ // also consider adding upgrade-insecure-requests once you have HTTPS in place for production
+ //csp += "upgrade-insecure-requests;";
+ // also an example if you need client images to be displayed from twitter
+ // csp += "img-src 'self' https://pbs.twimg.com;";
+
+ // once for standards compliant browsers
+ if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
+ {
+ context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp);
+ }
+ // and once again for IE
+ if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
+ {
+ context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
+ var referrer_policy = "no-referrer";
+ if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy"))
+ {
+ context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy);
+ }
+ }
+ }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs
new file mode 100644
index 0000000..f16672b
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityModel;
+using IdentityServer4.Test;
+using System.Collections.Generic;
+using System.Security.Claims;
+
+namespace IdentityServerHost.Quickstart.UI
+{
+ public class TestUsers
+ {
+ public static List Users
+ {
+ get
+ {
+ return new List
+ {
+ new TestUser
+ {
+ SubjectId = "3199711031234",
+ Username = "3199711031234",
+ Password = "123",
+ Claims =
+ {
+ new Claim(JwtClaimTypes.Name, "Muhammad Azeez"),
+ new Claim(JwtClaimTypes.GivenName, "Muhammad"),
+ new Claim(JwtClaimTypes.FamilyName, "Azeez"),
+ new Claim(JwtClaimTypes.Email, "muhammad-azeez@outlook.com"),
+ new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
+ }
+ }
+ };
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs
new file mode 100644
index 0000000..fa12954
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+
+using IdentityServerHost.Quickstart.UI;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace OidcSamples.AuthorizationServer
+{
+ public class Startup
+ {
+ public IWebHostEnvironment Environment { get; }
+
+ public Startup(IWebHostEnvironment environment)
+ {
+ Environment = environment;
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddControllersWithViews();
+
+ var builder = services.AddIdentityServer(options =>
+ {
+ // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
+ options.EmitStaticAudienceClaim = true;
+ })
+ .AddInMemoryIdentityResources(Config.IdentityResources)
+ .AddInMemoryApiResources(Config.ApiResources)
+ .AddInMemoryApiScopes(Config.ApiScopes)
+ .AddInMemoryClients(Config.Clients)
+ .AddTestUsers(TestUsers.Users);
+
+ // not recommended for production - you need to store your key material somewhere secure
+ builder.AddDeveloperSigningCredential();
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ if (Environment.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseStaticFiles();
+ app.UseRouting();
+
+ app.UseIdentityServer();
+
+ app.UseAuthorization();
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapDefaultControllerRoute();
+ });
+ }
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml
new file mode 100644
index 0000000..32e6c53
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml
@@ -0,0 +1,7 @@
+
+
+
+
Access Denied
+
You do not have access to that resource.
+
+
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml
new file mode 100644
index 0000000..3cc190b
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml
@@ -0,0 +1,34 @@
+@model LoggedOutViewModel
+
+@{
+ // set this so the layout rendering sees an anonymous user
+ ViewData["signed-out"] = true;
+}
+
+
+ Invalid login request
+ There are no login schemes configured for this request.
+
+ }
+
+
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml
new file mode 100644
index 0000000..e74bde6
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml
@@ -0,0 +1,15 @@
+@model LogoutViewModel
+
+
+ @foreach (var name in grant.IdentityGrantNames)
+ {
+
@name
+ }
+
+
+ }
+ @if (grant.ApiGrantNames.Any())
+ {
+
+
+
+ @foreach (var name in grant.ApiGrantNames)
+ {
+
@name
+ }
+
+
+ }
+
+
+ }
+ }
+
\ No newline at end of file
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml
new file mode 100644
index 0000000..36b2bfc
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml
@@ -0,0 +1,32 @@
+@using System.Diagnostics
+
+@{
+ var version = FileVersionInfo.GetVersionInfo(typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.Location).ProductVersion.Split('+').First();
+}
+
+
+
+
+ Welcome to IdentityServer4
+ (version @version)
+
+
+
+
+ IdentityServer publishes a
+ discovery document
+ where you can find metadata and links to all the endpoints, key material, etc.
+
+
+ Click here to see the claims for your current session.
+
+
+
+## Table of contents
+
+- [Quick start](#quick-start)
+- [Status](#status)
+- [What's included](#whats-included)
+- [Bugs and feature requests](#bugs-and-feature-requests)
+- [Documentation](#documentation)
+- [Contributing](#contributing)
+- [Community](#community)
+- [Versioning](#versioning)
+- [Creators](#creators)
+- [Thanks](#thanks)
+- [Copyright and license](#copyright-and-license)
+
+
+## Quick start
+
+Several quick start options are available:
+
+- [Download the latest release.](https://github.com/twbs/bootstrap/archive/v4.4.1.zip)
+- Clone the repo: `git clone https://github.com/twbs/bootstrap.git`
+- Install with [npm](https://www.npmjs.com/): `npm install bootstrap`
+- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@4.4.1`
+- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:4.4.1`
+- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass`
+
+Read the [Getting started page](https://getbootstrap.com/docs/4.4/getting-started/introduction/) for information on the framework contents, templates and examples, and more.
+
+
+## Status
+
+[](https://bootstrap-slack.herokuapp.com/)
+[](https://github.com/twbs/bootstrap/actions?workflow=Tests)
+[](https://www.npmjs.com/package/bootstrap)
+[](https://rubygems.org/gems/bootstrap)
+[](https://atmospherejs.com/twbs/bootstrap)
+[](https://packagist.org/packages/twbs/bootstrap)
+[](https://www.nuget.org/packages/bootstrap/absoluteLatest)
+[](https://david-dm.org/twbs/bootstrap?type=peer)
+[](https://david-dm.org/twbs/bootstrap?type=dev)
+[](https://coveralls.io/github/twbs/bootstrap?branch=v4-dev)
+[](https://github.com/twbs/bootstrap/tree/v4-dev/dist/css/bootstrap.min.css)
+[](https://github.com/twbs/bootstrap/tree/v4-dev/dist/js/bootstrap.min.js)
+[](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)
+[](#backers)
+[](#sponsors)
+
+
+## What's included
+
+Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. You'll see something like this:
+
+```text
+bootstrap/
+└── dist/
+ ├── css/
+ │ ├── bootstrap-grid.css
+ │ ├── bootstrap-grid.css.map
+ │ ├── bootstrap-grid.min.css
+ │ ├── bootstrap-grid.min.css.map
+ │ ├── bootstrap-reboot.css
+ │ ├── bootstrap-reboot.css.map
+ │ ├── bootstrap-reboot.min.css
+ │ ├── bootstrap-reboot.min.css.map
+ │ ├── bootstrap.css
+ │ ├── bootstrap.css.map
+ │ ├── bootstrap.min.css
+ │ └── bootstrap.min.css.map
+ └── js/
+ ├── bootstrap.bundle.js
+ ├── bootstrap.bundle.js.map
+ ├── bootstrap.bundle.min.js
+ ├── bootstrap.bundle.min.js.map
+ ├── bootstrap.js
+ ├── bootstrap.js.map
+ ├── bootstrap.min.js
+ └── bootstrap.min.js.map
+```
+
+We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/).
+
+
+## Bugs and feature requests
+
+Have a bug or a feature request? Please first read the [issue guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/twbs/bootstrap/issues/new).
+
+
+## Documentation
+
+Bootstrap's documentation, included in this repo in the root directory, is built with [Jekyll](https://jekyllrb.com/) and publicly hosted on GitHub Pages at . The docs may also be run locally.
+
+Documentation search is powered by [Algolia's DocSearch](https://community.algolia.com/docsearch/). Working on our search? Be sure to set `debug: true` in `site/docs/4.4/assets/js/src/search.js` file.
+
+### Running documentation locally
+
+1. Run through the [tooling setup](https://getbootstrap.com/docs/4.4/getting-started/build-tools/#tooling-setup) to install Jekyll (the site builder) and other Ruby dependencies with `bundle install`.
+2. Run `npm install` to install Node.js dependencies.
+3. Run `npm start` to compile CSS and JavaScript files, generate our docs, and watch for changes.
+4. Open `http://localhost:9001` in your browser, and voilà.
+
+Learn more about using Jekyll by reading its [documentation](https://jekyllrb.com/docs/).
+
+### Documentation for previous releases
+
+You can find all our previous releases docs on .
+
+[Previous releases](https://github.com/twbs/bootstrap/releases) and their documentation are also available for download.
+
+
+## Contributing
+
+Please read through our [contributing guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development.
+
+Moreover, if your pull request contains JavaScript patches or features, you must include [relevant unit tests](https://github.com/twbs/bootstrap/tree/master/js/tests). All HTML and CSS should conform to the [Code Guide](https://github.com/mdo/code-guide), maintained by [Mark Otto](https://github.com/mdo).
+
+Editor preferences are available in the [editor config](https://github.com/twbs/bootstrap/blob/master/.editorconfig) for easy use in common text editors. Read more and download plugins at .
+
+
+## Community
+
+Get updates on Bootstrap's development and chat with the project maintainers and community members.
+
+- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap).
+- Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com/).
+- Join [the official Slack room](https://bootstrap-slack.herokuapp.com/).
+- Chat with fellow Bootstrappers in IRC. On the `irc.freenode.net` server, in the `##bootstrap` channel.
+- Implementation help may be found at Stack Overflow (tagged [`bootstrap-4`](https://stackoverflow.com/questions/tagged/bootstrap-4)).
+- Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability.
+
+
+## Versioning
+
+For transparency into our release cycle and in striving to maintain backward compatibility, Bootstrap is maintained under [the Semantic Versioning guidelines](https://semver.org/). Sometimes we screw up, but we adhere to those rules whenever possible.
+
+See [the Releases section of our GitHub project](https://github.com/twbs/bootstrap/releases) for changelogs for each release version of Bootstrap. Release announcement posts on [the official Bootstrap blog](https://blog.getbootstrap.com/) contain summaries of the most noteworthy changes made in each release.
+
+
+## Creators
+
+**Mark Otto**
+
+-
+-
+
+**Jacob Thornton**
+
+-
+-
+
+
+## Thanks
+
+
+
+
+
+Thanks to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to test in real browsers!
+
+
+## Backers
+
+Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/bootstrap#backer)]
+
+[](https://opencollective.com/bootstrap#backers)
+
+
+## Sponsors
+
+Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/bootstrap#sponsor)]
+
+[](https://opencollective.com/bootstrap/sponsor/0/website)
+[](https://opencollective.com/bootstrap/sponsor/1/website)
+[](https://opencollective.com/bootstrap/sponsor/2/website)
+[](https://opencollective.com/bootstrap/sponsor/3/website)
+[](https://opencollective.com/bootstrap/sponsor/4/website)
+[](https://opencollective.com/bootstrap/sponsor/5/website)
+[](https://opencollective.com/bootstrap/sponsor/6/website)
+[](https://opencollective.com/bootstrap/sponsor/7/website)
+[](https://opencollective.com/bootstrap/sponsor/8/website)
+[](https://opencollective.com/bootstrap/sponsor/9/website)
+
+
+## Copyright and license
+
+Code and documentation copyright 2011-2019 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/master/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss
new file mode 100644
index 0000000..da2a98a
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss
@@ -0,0 +1,51 @@
+//
+// Base styles
+//
+
+.alert {
+ position: relative;
+ padding: $alert-padding-y $alert-padding-x;
+ margin-bottom: $alert-margin-bottom;
+ border: $alert-border-width solid transparent;
+ @include border-radius($alert-border-radius);
+}
+
+// Headings for larger alerts
+.alert-heading {
+ // Specified to prevent conflicts of changing $headings-color
+ color: inherit;
+}
+
+// Provide class for links that match alerts
+.alert-link {
+ font-weight: $alert-link-font-weight;
+}
+
+
+// Dismissible alerts
+//
+// Expand the right padding and account for the close button's positioning.
+
+.alert-dismissible {
+ padding-right: $close-font-size + $alert-padding-x * 2;
+
+ // Adjust close link position
+ .close {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: $alert-padding-y $alert-padding-x;
+ color: inherit;
+ }
+}
+
+
+// Alternate styles
+//
+// Generate contextual modifier classes for colorizing the alert.
+
+@each $color, $value in $theme-colors {
+ .alert-#{$color} {
+ @include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level));
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss
new file mode 100644
index 0000000..42c5d08
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss
@@ -0,0 +1,54 @@
+// Base class
+//
+// Requires one of the contextual, color modifier classes for `color` and
+// `background-color`.
+
+.badge {
+ display: inline-block;
+ padding: $badge-padding-y $badge-padding-x;
+ @include font-size($badge-font-size);
+ font-weight: $badge-font-weight;
+ line-height: 1;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ @include border-radius($badge-border-radius);
+ @include transition($badge-transition);
+
+ @at-root a#{&} {
+ @include hover-focus() {
+ text-decoration: none;
+ }
+ }
+
+ // Empty badges collapse automatically
+ &:empty {
+ display: none;
+ }
+}
+
+// Quick fix for badges in buttons
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+
+// Pill badges
+//
+// Make them extra rounded with a modifier to replace v3's badges.
+
+.badge-pill {
+ padding-right: $badge-pill-padding-x;
+ padding-left: $badge-pill-padding-x;
+ @include border-radius($badge-pill-border-radius);
+}
+
+// Colors
+//
+// Contextual variations (linked badges get darker on :hover).
+
+@each $color, $value in $theme-colors {
+ .badge-#{$color} {
+ @include badge-variant($value);
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss
new file mode 100644
index 0000000..d748894
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss
@@ -0,0 +1,42 @@
+.breadcrumb {
+ display: flex;
+ flex-wrap: wrap;
+ padding: $breadcrumb-padding-y $breadcrumb-padding-x;
+ margin-bottom: $breadcrumb-margin-bottom;
+ @include font-size($breadcrumb-font-size);
+ list-style: none;
+ background-color: $breadcrumb-bg;
+ @include border-radius($breadcrumb-border-radius);
+}
+
+.breadcrumb-item {
+ // The separator between breadcrumbs (by default, a forward-slash: "/")
+ + .breadcrumb-item {
+ padding-left: $breadcrumb-item-padding;
+
+ &::before {
+ display: inline-block; // Suppress underlining of the separator in modern browsers
+ padding-right: $breadcrumb-item-padding;
+ color: $breadcrumb-divider-color;
+ content: escape-svg($breadcrumb-divider);
+ }
+ }
+
+ // IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built
+ // without `
`s. The `::before` pseudo-element generates an element
+ // *within* the .breadcrumb-item and thereby inherits the `text-decoration`.
+ //
+ // To trick IE into suppressing the underline, we give the pseudo-element an
+ // underline and then immediately remove it.
+ + .breadcrumb-item:hover::before {
+ text-decoration: underline;
+ }
+ // stylelint-disable-next-line no-duplicate-selectors
+ + .breadcrumb-item:hover::before {
+ text-decoration: none;
+ }
+
+ &.active {
+ color: $breadcrumb-active-color;
+ }
+}
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_button-group.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_button-group.scss
new file mode 100644
index 0000000..da02d79
--- /dev/null
+++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_button-group.scss
@@ -0,0 +1,163 @@
+// stylelint-disable selector-no-qualifying-type
+
+// Make the div behave like a button
+.btn-group,
+.btn-group-vertical {
+ position: relative;
+ display: inline-flex;
+ vertical-align: middle; // match .btn alignment given font-size hack above
+
+ > .btn {
+ position: relative;
+ flex: 1 1 auto;
+
+ // Bring the hover, focused, and "active" buttons to the front to overlay
+ // the borders properly
+ @include hover() {
+ z-index: 1;
+ }
+ &:focus,
+ &:active,
+ &.active {
+ z-index: 1;
+ }
+ }
+}
+
+// Optional: Group multiple button groups together for a toolbar
+.btn-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+
+ .input-group {
+ width: auto;
+ }
+}
+
+.btn-group {
+ // Prevent double borders when buttons are next to each other
+ > .btn:not(:first-child),
+ > .btn-group:not(:first-child) {
+ margin-left: -$btn-border-width;
+ }
+
+ // Reset rounded corners
+ > .btn:not(:last-child):not(.dropdown-toggle),
+ > .btn-group:not(:last-child) > .btn {
+ @include border-right-radius(0);
+ }
+
+ > .btn:not(:first-child),
+ > .btn-group:not(:first-child) > .btn {
+ @include border-left-radius(0);
+ }
+}
+
+// Sizing
+//
+// Remix the default button sizing classes into new ones for easier manipulation.
+
+.btn-group-sm > .btn { @extend .btn-sm; }
+.btn-group-lg > .btn { @extend .btn-lg; }
+
+
+//
+// Split button dropdowns
+//
+
+.dropdown-toggle-split {
+ padding-right: $btn-padding-x * .75;
+ padding-left: $btn-padding-x * .75;
+
+ &::after,
+ .dropup &::after,
+ .dropright &::after {
+ margin-left: 0;
+ }
+
+ .dropleft &::before {
+ margin-right: 0;
+ }
+}
+
+.btn-sm + .dropdown-toggle-split {
+ padding-right: $btn-padding-x-sm * .75;
+ padding-left: $btn-padding-x-sm * .75;
+}
+
+.btn-lg + .dropdown-toggle-split {
+ padding-right: $btn-padding-x-lg * .75;
+ padding-left: $btn-padding-x-lg * .75;
+}
+
+
+// The clickable button for toggling the menu
+// Set the same inset shadow as the :active state
+.btn-group.show .dropdown-toggle {
+ @include box-shadow($btn-active-box-shadow);
+
+ // Show no shadow for `.btn-link` since it has no other button styles.
+ &.btn-link {
+ @include box-shadow(none);
+ }
+}
+
+
+//
+// Vertical button groups
+//
+
+.btn-group-vertical {
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+
+ > .btn,
+ > .btn-group {
+ width: 100%;
+ }
+
+ > .btn:not(:first-child),
+ > .btn-group:not(:first-child) {
+ margin-top: -$btn-border-width;
+ }
+
+ // Reset rounded corners
+ > .btn:not(:last-child):not(.dropdown-toggle),
+ > .btn-group:not(:last-child) > .btn {
+ @include border-bottom-radius(0);
+ }
+
+ > .btn:not(:first-child),
+ > .btn-group:not(:first-child) > .btn {
+ @include border-top-radius(0);
+ }
+}
+
+
+// Checkbox and radio options
+//
+// In order to support the browser's form validation feedback, powered by the
+// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
+// `display: none;` or `visibility: hidden;` as that also hides the popover.
+// Simply visually hiding the inputs via `opacity` would leave them clickable in
+// certain cases which is prevented by using `clip` and `pointer-events`.
+// This way, we ensure a DOM element is visible to position the popover from.
+//
+// See https://github.com/twbs/bootstrap/pull/12794 and
+// https://github.com/twbs/bootstrap/pull/14559 for more information.
+
+.btn-group-toggle {
+ > .btn,
+ > .btn-group > .btn {
+ margin-bottom: 0; // Override default `