This is scenario where we just want to use FIDO as the MFA. The user register and logins with their username and password. For demo purposes, we trigger the MFA registering on sign up.
├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── IdentityProvider ├── Areas │ └── Identity │ │ ├── IdentityHostingStartup.cs │ │ └── Pages │ │ ├── Account │ │ ├── Login.cshtml │ │ ├── Login.cshtml.cs │ │ ├── LoginFido2Mfa.cshtml │ │ ├── LoginFido2Mfa.cshtml.cs │ │ ├── Manage │ │ │ ├── Disable2fa.cshtml │ │ │ ├── Disable2fa.cshtml.cs │ │ │ ├── Fido2Mfa.cshtml │ │ │ ├── Fido2Mfa.cshtml.cs │ │ │ ├── ManageNavPages.cs │ │ │ ├── TwoFactorAuthentication.cshtml │ │ │ ├── TwoFactorAuthentication.cshtml.cs │ │ │ ├── _ManageNav.cshtml │ │ │ └── _ViewImports.cshtml │ │ └── _ViewImports.cshtml │ │ ├── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml ├── Controllers │ ├── AuthorizationController.cs │ ├── ErrorController.cs │ ├── HomeController.cs │ ├── ResourceController.cs │ └── UserinfoController.cs ├── Data │ ├── ApplicationDbContext.cs │ └── ApplicationUser.cs ├── Fido2 │ ├── Fido2Store.cs │ ├── Fido2UserTwoFactorTokenProvider.cs │ ├── FidoStoredCredential.cs │ ├── MfaFido2RegisterController.cs │ ├── MfaFido2SignInFidoController.cs │ ├── PwFido2RegisterController.cs │ └── PwFido2SignInController.cs ├── Helpers │ ├── AsyncEnumerableExtensions.cs │ └── FormValueRequiredAttribute.cs ├── IdentityProvider.csproj ├── Migrations │ ├── 20220827060047_add-fido2.Designer.cs │ ├── 20220827060047_add-fido2.cs │ ├── 20231230171959_init_sts.Designer.cs │ ├── 20231230171959_init_sts.cs │ └── ApplicationDbContextModelSnapshot.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── StartupExtensions.cs ├── ViewModels │ ├── Authorization │ │ └── AuthorizeViewModel.cs │ └── Shared │ │ └── ErrorViewModel.cs ├── Views │ ├── Authorization │ │ ├── Authorize.cshtml │ │ └── Logout.cshtml │ ├── Home │ │ ├── Index.cshtml │ │ └── Privacy.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ ├── _Layout.cshtml.css │ │ └── _LoginPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Worker.cs ├── appsettings.json ├── usersdatabase.sqlite └── wwwroot │ ├── css │ └── site.css │ ├── favicon.ico │ ├── images │ ├── securitykey.min.svg │ └── securitykey.svg │ ├── js │ ├── helpers.js │ ├── instant.js │ ├── mfa.login.js │ ├── mfa.register.js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-grid.rtl.css │ │ ├── bootstrap-grid.rtl.css.map │ │ ├── bootstrap-grid.rtl.min.css │ │ ├── bootstrap-grid.rtl.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap-reboot.rtl.css │ │ ├── bootstrap-reboot.rtl.css.map │ │ ├── bootstrap-reboot.rtl.min.css │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ ├── bootstrap-utilities.css │ │ ├── bootstrap-utilities.css.map │ │ ├── bootstrap-utilities.min.css │ │ ├── bootstrap-utilities.min.css.map │ │ ├── bootstrap-utilities.rtl.css │ │ ├── bootstrap-utilities.rtl.css.map │ │ ├── bootstrap-utilities.rtl.min.css │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ ├── bootstrap.rtl.css │ │ ├── bootstrap.rtl.css.map │ │ ├── bootstrap.rtl.min.css │ │ └── bootstrap.rtl.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.esm.js │ │ ├── bootstrap.esm.js.map │ │ ├── bootstrap.esm.min.js │ │ ├── bootstrap.esm.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ └── dist │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ ├── jquery.min.map │ ├── jquery.slim.js │ ├── jquery.slim.min.js │ └── jquery.slim.min.map ├── LICENSE ├── README.md ├── bff-openiddict.sln └── bff ├── .gitignore ├── server ├── BffOpenIddict.Server.csproj ├── Controllers │ ├── AccountController.cs │ ├── DirectApiController.cs │ └── UserController.cs ├── Models │ ├── ClaimValue.cs │ └── UserInfo.cs ├── Pages │ ├── Error.cshtml │ ├── Error.cshtml.cs │ └── _Host.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── SecurityHeadersDefinitions.cs ├── Services │ ├── ApplicationBuilderExtensions.cs │ └── EndpointRouteBuilderExtensions.cs ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── 3rdpartylicenses.txt │ ├── favicon.ico │ ├── index.html │ ├── main.30351b27ad0c83f9.js │ ├── polyfills.8cdf208185fb5862.js │ ├── runtime.1cb9cb05f2deb873.js │ └── styles.ef46db3751d8e999.css └── ui ├── .eslintignore ├── .eslintrc.base.json ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── certs ├── Readme.md ├── dev_localhost.key └── dev_localhost.pem ├── e2e ├── .eslintrc.json ├── playwright.config.ts ├── project.json └── src │ └── example.spec.ts ├── jest.config.ts ├── jest.preset.js ├── migrations.json ├── nx.json ├── package-lock.json ├── package.json ├── project.json ├── src ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.config.ts │ ├── app.routes.ts │ ├── call-api │ │ ├── call-api.component.html │ │ ├── call-api.component.scss │ │ ├── call-api.component.spec.ts │ │ └── call-api.component.ts │ ├── getCookie.ts │ ├── home.component.html │ ├── home.component.ts │ └── secure-api.interceptor.ts ├── assets │ └── .gitkeep ├── favicon.ico ├── index.html ├── main.ts ├── styles.scss └── test-setup.ts ├── tsconfig.app.json ├── tsconfig.editor.json ├── tsconfig.json └── tsconfig.spec.json /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | 2 | name: .NET and npm build 3 | 4 | on: 5 | push: 6 | branches: [ "main" ] 7 | pull_request: 8 | branches: [ "main" ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | 16 | - uses: actions/checkout@v4 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v4 19 | with: 20 | dotnet-version: 9.0.x 21 | 22 | - name: Restore dependencies 23 | run: dotnet restore 24 | 25 | - name: npm setup 26 | working-directory: bff/ui 27 | run: npm install --force 28 | 29 | - name: ui-nx-build 30 | working-directory: bff/ui 31 | run: npm run build 32 | 33 | - name: Build 34 | run: dotnet build --no-restore 35 | - name: Test 36 | run: dotnet test --no-build --verbosity normal 37 | -------------------------------------------------------------------------------- /IdentityProvider/Areas/Identity/IdentityHostingStartup.cs: -------------------------------------------------------------------------------- 1 | [assembly: HostingStartup(typeof(OpeniddictServer.Areas.Identity.IdentityHostingStartup))] 2 | namespace OpeniddictServer.Areas.Identity 3 | { 4 | public class IdentityHostingStartup : IHostingStartup 5 | { 6 | public void Configure(IWebHostBuilder builder) 7 | { 8 | builder.ConfigureServices((context, services) => 9 | { 10 | }); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /IdentityProvider/Areas/Identity/Pages/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginModel 3 | 4 | @{ 5 | ViewData["Title"] = "Log in"; 6 | } 7 | 8 |
60 | There are no external authentication services configured. See this article 61 | about setting up this ASP.NET application to support logging in via external services. 62 |
63 |This is scenario where we just want to use FIDO as the MFA. The user register and logins with their username and password. For demo purposes, we trigger the MFA registering on sign up.
13 | This action only disables 2FA. 14 |
15 |16 | Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key 17 | used in an authenticator app you should reset your authenticator keys. 18 |
19 |This is scenario where we just want to use FIDO as the MFA. The user register and logins with their username and password. For demo purposes, we trigger the MFA registering on sign up.
You must generate a new set of recovery codes before you can log in with a recovery code.
22 |You can generate a new set of recovery codes.
29 |You should generate a new set of recovery codes.
36 |You must accept the policy before you can enable two factor authentication.
65 |Do you want to grant @Model.ApplicationName access to your data? (scopes requested: @Model.Scope)
8 | 9 | 20 |Are you sure you want to sign out?
6 | 7 | 17 |Learn about building Web apps with ASP.NET Core.
8 |Use this page to detail your site's privacy policy.
7 | -------------------------------------------------------------------------------- /IdentityProvider/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | 3 |6 | @if (!string.IsNullOrEmpty(Model.Error)) { 7 | @Model.Error 8 | } 9 | 10 | @if (!string.IsNullOrEmpty(Model.ErrorDescription)) { 11 | @Model.ErrorDescription 12 | } 13 |
14 |Processing
'; 81 | 82 | try { 83 | await verifyAssertionWithServer(credential); 84 | } catch (e) { 85 | document.getElementById('fido2logindisplay').innerHTML = ''; 86 | const fido2CouldNotVerifyAssertion = document.getElementById('fido2CouldNotVerifyAssertion').innerText; 87 | showErrorAlert(fido2CouldNotVerifyAssertion, e); 88 | } 89 | } 90 | 91 | async function verifyAssertionWithServer(assertedCredential) { 92 | // Move data into Arrays incase it is super long 93 | let authData = new Uint8Array(assertedCredential.response.authenticatorData); 94 | let clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON); 95 | let rawId = new Uint8Array(assertedCredential.rawId); 96 | let sig = new Uint8Array(assertedCredential.response.signature); 97 | let userHandle = new Uint8Array(assertedCredential.response.userHandle); 98 | const data = { 99 | id: assertedCredential.id, 100 | rawId: coerceToBase64Url(rawId), 101 | type: assertedCredential.type, 102 | extensions: assertedCredential.getClientExtensionResults(), 103 | response: { 104 | authenticatorData: coerceToBase64Url(authData), 105 | clientDataJson: coerceToBase64Url(clientDataJSON), 106 | //userHandle: userHandle !== null ? coerceToBase64Url(userHandle) : null, 107 | signature: coerceToBase64Url(sig) 108 | } 109 | }; 110 | 111 | let response; 112 | try { 113 | let res = await fetch("/mfamakeAssertion", { 114 | method: 'POST', // or 'PUT' 115 | body: JSON.stringify(data), // data can be `string` or {object}! 116 | headers: { 117 | 'Accept': 'application/json', 118 | 'Content-Type': 'application/json', 119 | 'RequestVerificationToken': document.getElementById('RequestVerificationToken').value 120 | } 121 | }); 122 | 123 | response = await res.json(); 124 | } catch (e) { 125 | showErrorAlert("Request to server failed", e); 126 | throw e; 127 | } 128 | 129 | //console.log("Assertion Object", response); 130 | 131 | // show error 132 | if (response.status !== "ok") { 133 | console.log("Error doing assertion"); 134 | console.log(response.errorMessage); 135 | document.getElementById('fido2logindisplay').innerHTML = ''; 136 | showErrorAlert(response.errorMessage); 137 | return; 138 | } 139 | 140 | //document.getElementById('fido2logindisplay').innerHTML = 'Logged In!
'; 141 | 142 | // show success message 143 | //await Swal.fire({ 144 | // title: 'Logged In!', 145 | // text: 'You\'re logged in successfully.', 146 | // //type: 'success', 147 | // timer: 2000 148 | //}); 149 | let fido2ReturnUrl = document.getElementById('fido2ReturnUrl').innerText; 150 | if (!fido2ReturnUrl) { 151 | fido2ReturnUrl = "/"; 152 | } 153 | window.location.href = fido2ReturnUrl; 154 | } 155 | -------------------------------------------------------------------------------- /IdentityProvider/wwwroot/js/mfa.register.js: -------------------------------------------------------------------------------- 1 | document.getElementById('register').addEventListener('submit', handleRegisterSubmit); 2 | 3 | async function handleRegisterSubmit(event) { 4 | event.preventDefault(); 5 | 6 | let username = this.username.value; 7 | //let displayName = this.displayName.value; 8 | // passwordfield is omitted in demo 9 | // let password = this.password.value; 10 | // possible values: none, direct, indirect 11 | let attestation_type = "none"; 12 | // possible values:
22 | Request ID: @Model.RequestId
23 |
28 | Swapping to the Development environment displays detailed information about the error that occurred. 29 |
30 |31 | The Development environment shouldn't be enabled for deployed applications. 32 | It can result in displaying sensitive information from exceptions to end users. 33 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 34 | and restarting the app. 35 |
36 |