{"id":15410597,"url":"https://github.com/damienbod/blazor.bff.azuread.template","last_synced_at":"2025-03-31T04:03:48.983Z","repository":{"id":39970615,"uuid":"433166540","full_name":"damienbod/Blazor.BFF.AzureAD.Template","owner":"damienbod","description":"Blazor.BFF.AzureAD.Template,  Blazor WASM hosted in ASP.NET Core using Microsoft Entra ID BFF (server authentication)","archived":false,"fork":false,"pushed_at":"2024-10-13T07:39:15.000Z","size":4561,"stargazers_count":85,"open_issues_count":3,"forks_count":14,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-04T13:46:18.798Z","etag":null,"topics":["aad","azuread","blazor","csp","dotnet","microsoftentraid","microsoftidentity","oauth2","oidc","template"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/Blazor.BFF.AzureAD.Template/","language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/damienbod.png","metadata":{"files":{"readme":"README-NUGET.md","changelog":"Changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-29T19:12:40.000Z","updated_at":"2025-03-04T07:50:03.000Z","dependencies_parsed_at":"2023-11-12T11:31:06.795Z","dependency_job_id":"e8761cc7-1024-4421-b567-d70c10fd09cb","html_url":"https://github.com/damienbod/Blazor.BFF.AzureAD.Template","commit_stats":{"total_commits":171,"total_committers":1,"mean_commits":171.0,"dds":0.0,"last_synced_commit":"18b65fbbfe9c9866c6f5e7fda291db74016473f1"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damienbod%2FBlazor.BFF.AzureAD.Template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damienbod%2FBlazor.BFF.AzureAD.Template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damienbod%2FBlazor.BFF.AzureAD.Template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damienbod%2FBlazor.BFF.AzureAD.Template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/damienbod","download_url":"https://codeload.github.com/damienbod/Blazor.BFF.AzureAD.Template/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246413232,"owners_count":20773053,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aad","azuread","blazor","csp","dotnet","microsoftentraid","microsoftidentity","oauth2","oidc","template"],"created_at":"2024-10-01T16:45:14.094Z","updated_at":"2025-03-31T04:03:48.953Z","avatar_url":"https://github.com/damienbod.png","language":"CSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Blazor.BFF.AzureAD.Template\r\n\r\n[![.NET](https://github.com/damienbod/Blazor.BFF.AzureAD.Template/actions/workflows/dotnet.yml/badge.svg)](https://github.com/damienbod/Blazor.BFF.AzureAD.Template/actions/workflows/dotnet.yml) [![NuGet Status](http://img.shields.io/nuget/v/Blazor.BFF.AzureAD.Template.svg?style=flat-square)](https://www.nuget.org/packages/Blazor.BFF.AzureAD.Template/) [Change log](https://github.com/damienbod/Blazor.BFF.AzureAD.Template/blob/main/Changelog.md)\r\n\r\nThis template can be used to create a Blazor WASM application hosted in an ASP.NET Core Web app using Microsoft Entra ID and Microsoft.Identity.Web to authenticate using the BFF security architecture. (server authentication) This removes the tokens from the browser and uses cookies with each HTTP request, response. The template also adds the required security headers as best it can for a Blazor application.\r\n\r\n## Features\r\n\r\n- WASM hosted in ASP.NET Core 8\r\n- BFF with Microsoft Entra ID using Microsoft.Identity.Web\r\n- OAuth2 and OpenID Connect OIDC\r\n- No tokens in the browser\r\n- Microsoft Entra ID Continuous Access Evaluation CAE support\r\n\r\n## Using the template\r\n\r\n### install\r\n\r\n```\r\ndotnet new install Blazor.BFF.AzureAD.Template\r\n```\r\n\r\n### run\r\n\r\n```\r\ndotnet new blazorbffaad -n YourCompany.Bff\r\n```\r\n\r\nUse the `-n` or `--name` parameter to change the name of the output created. This string is also used to substitute the namespace name in the .cs file for the project.\r\n\r\n## Setup after installation\r\n\r\nAdd the Microsoft Entra ID App registration settings\r\n\r\n```\r\n{\r\n  \"AzureAd\": {\r\n    \"Instance\": \"https://login.microsoftonline.com/\",\r\n    \"Domain\": \"[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]\",\r\n    \"TenantId\": \"[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]\",\r\n    \"ClientId\": \"[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]\",\r\n    \"ClientSecret\": \"[Copy the client secret added to the app from the Azure portal]\",\r\n    \"ClientCertificates\": [\r\n    ],\r\n    // the following is required to handle Continuous Access Evaluation challenges\r\n    \"ClientCapabilities\": [ \"cp1\" ],\r\n    \"CallbackPath\": \"/signin-oidc\"\r\n  },\r\n```\r\n\r\nAdd the scopes for the downstream API if required\r\n\r\n```\r\n  \"DownstreamApi\": {\r\n    \"Scopes\": \"User.ReadBasic.All user.read\"\r\n  },\r\n```\r\n\r\n### Use Continuous Access Evaluation CAE with a downstream API (access_token)\r\n\r\n#### Azure app registration manifest\r\n\r\n```json\r\n\"optionalClaims\": {\r\n\t\"idToken\": [],\r\n\t\"accessToken\": [\r\n\t\t{\r\n\t\t\t\"name\": \"xms_cc\",\r\n\t\t\t\"source\": null,\r\n\t\t\t\"essential\": false,\r\n\t\t\t\"additionalProperties\": []\r\n\t\t}\r\n\t],\r\n\t\"saml2Token\": []\r\n},\r\n```\r\n\r\nAny API call for the Blazor WASM could be implemented like this:\r\n\r\n```\r\n[HttpGet]\r\npublic async Task\u003cIActionResult\u003e Get()\r\n{\r\n  try\r\n  {\r\n\t// Do logic which calls an API and throws claims challenge \r\n\t// WebApiMsalUiRequiredException. The WWW-Authenticate header is set\r\n\t// using the OpenID Connect standards and Signals spec.\r\n  }\r\n  catch (WebApiMsalUiRequiredException hex)\r\n  {\r\n\tvar claimChallenge = WwwAuthenticateParameters\r\n\t\t.GetClaimChallengeFromResponseHeaders(hex.Headers);\r\n\t\t\r\n\treturn Unauthorized(claimChallenge);\r\n  }\r\n}\r\n```\r\n\r\nThe downstream API call could be implemented something like this:\r\n\r\n```\r\npublic async Task\u003cT\u003e CallApiAsync(string url)\r\n{\r\n\tvar client = _clientFactory.CreateClient();\r\n\r\n\t// ... add bearer token\r\n\t\r\n\tvar response = await client.GetAsync(url);\r\n\tif (response.IsSuccessStatusCode)\r\n\t{\r\n\t\tvar stream = await response.Content.ReadAsStreamAsync();\r\n\t\tvar payload = await JsonSerializer.DeserializeAsync\u003cT\u003e(stream);\r\n\r\n\t\treturn payload;\r\n\t}\r\n\r\n\t// You can check the WWW-Authenticate header first, if it is a CAE challenge\r\n\t\r\n\tthrow new WebApiMsalUiRequiredException($\"Error: {response.StatusCode}.\", response);\r\n}\r\n```\r\n\r\n### Use Continuous Access Evaluation CAE in a standalone app (id_token)\r\n\r\n#### Azure app registration manifest\r\n\r\n```json\r\n\"optionalClaims\": {\r\n\t\"idToken\": [\r\n\t\t{\r\n\t\t\t\"name\": \"xms_cc\",\r\n\t\t\t\"source\": null,\r\n\t\t\t\"essential\": false,\r\n\t\t\t\"additionalProperties\": []\r\n\t\t}\r\n\t],\r\n\t\"accessToken\": [],\r\n\t\"saml2Token\": []\r\n},\r\n```\r\n\r\nIf using a CAE Authcontext in a standalone project, you only need to challenge against the claims in the application.\r\n\r\n```\r\nprivate readonly CaeClaimsChallengeService _caeClaimsChallengeService;\r\n\r\npublic AdminApiCallsController(CaeClaimsChallengeService caeClaimsChallengeService)\r\n{\r\n  _caeClaimsChallengeService = caeClaimsChallengeService;\r\n}\r\n\r\n[HttpGet]\r\npublic IActionResult Get()\r\n{\r\n  // if CAE claim missing in id token, the required claims challenge is returned\r\n  var claimsChallenge = _caeClaimsChallengeService\r\n\t.CheckForRequiredAuthContextIdToken(AuthContextId.C1, HttpContext);\r\n\r\n  if (claimsChallenge != null)\r\n  {\r\n\treturn Unauthorized(claimsChallenge);\r\n  }\r\n```\r\n\r\n\r\n\r\n### uninstall\r\n\r\n```\r\ndotnet new uninstall Blazor.BFF.AzureAD.Template\r\n```\r\n\r\n## Credits, Used NuGet packages + ASP.NET Core 8.0 standard packages\r\n\r\n- NetEscapades.AspNetCore.SecurityHeaders\r\n\r\n## Links\r\n\r\nhttps://github.com/AzureAD/microsoft-identity-web\r\n\r\nhttps://damienbod.com/2022/04/20/implement-azure-ad-continuous-access-evaluation-in-an-asp-net-core-razor-page-app-using-a-web-api/\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdamienbod%2Fblazor.bff.azuread.template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdamienbod%2Fblazor.bff.azuread.template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdamienbod%2Fblazor.bff.azuread.template/lists"}