diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index 767dfcf9c61f..bed1138e4093 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -138,12 +138,22 @@ jwtOptions.Authority = "{AUTHORITY}"; The following examples use a Tenant ID of `aaaabbbb-0000-cccc-1111-dddd2222eeee` and a directory name of `contoso`. -If the app is registered in an ME-ID tenant, the authority should match the issurer (`iss`) of the JWT returned by the identity provider: +If the app is registered in an ME-ID tenant, the authority should match the issuer (`iss`) of the JWT returned by the identity provider. + +V1 STS token endpoint: ```csharp -jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee"; +jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/"; ``` +V2 STS token endpoint: + +```csharp +jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0"; +``` + +For more information on V2 STS tokens, see the [STS token version](#sts-token-version) section. + If the app is registered in a Microsoft Entra External ID tenant: ```csharp @@ -434,12 +444,22 @@ jwtOptions.Authority = "{AUTHORITY}"; The following examples use a Tenant ID of `aaaabbbb-0000-cccc-1111-dddd2222eeee` and a directory name of `contoso`. -If the app is registered in an ME-ID tenant, the authority should match the issurer (`iss`) of the JWT returned by the identity provider: +If the app is registered in an ME-ID tenant, the authority should match the issuer (`iss`) of the JWT returned by the identity provider. + +V1 STS token endpoint: + +```csharp +jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/"; +``` + +V2 STS token endpoint: ```csharp -jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee"; +jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0"; ``` +For more information on V2 STS tokens, see the [STS token version](#sts-token-version) section. + If the app is registered in a Microsoft Entra External ID tenant: ```csharp @@ -842,13 +862,15 @@ In the `MinimalApiJwt` project, add the following app settings configuration to "Authentication": { "Schemes": { "Bearer": { - "Authority": "https://sts.windows.net/{TENANT ID (WEB API)}", + "Authority": "https://sts.windows.net/{TENANT ID (WEB API)}/", "ValidAudiences": ["{APP ID URI (WEB API)}"] } } }, ``` +The preceding example uses the V1 STS token URL format. For guidance on V2 STS tokens, see the [STS token version](#sts-token-version) section. + Update the placeholders in the preceding configuration to match the values that the app uses in the `Program` file: * `{TENANT ID (WEB API)}`: The Tenant Id of the web API. @@ -856,10 +878,12 @@ Update the placeholders in the preceding configuration to match the values that Authority formats adopt the following patterns: -* ME-ID tenant type: `https://sts.windows.net/{TENANT ID}` +* ME-ID tenant type: `https://sts.windows.net/{TENANT ID}/` * Microsoft Entra External ID: `https://{DIRECTORY NAME}.ciamlogin.com/{TENANT ID}/v2.0` * B2C tenant type: `https://login.microsoftonline.com/{TENANT ID}/v2.0` +The preceding example for the ME-ID tenant type uses the V1 STS token URL format. For guidance on V2 STS tokens, see the [STS token version](#sts-token-version) section. + Audience formats adopt the following patterns (`{CLIENT ID}` is the Client Id of the web API; `{DIRECTORY NAME}` is the directory name, for example, `contoso`): * ME-ID tenant type: `api://{CLIENT ID}` @@ -1157,6 +1181,45 @@ Server-side Blazor Web Apps hosted in a web farm or cluster of machines must ado We also recommend using a shared [Data Protection](xref:security/data-protection/introduction) key ring in production, even when the app uses the Interactive WebAssembly render mode exclusively for client-side rendering (no Blazor circuits). +## STS token version + +There are two types of token URIs, named Version 1 (V1) and Version 2 (V2). In Azure's security token services (STS), the V1 endpoint uses the `sts.windows.net` domain as the issuer, while the V2 endpoint uses the `login.microsoftonline.com` domain as the issuer. V2 supports additional features, such as authenticating personal accounts and OpenID Connect (OIDC) protocols. + +This article and its accompanying sample apps adopt V1 STS tokens. To adopt V2 tokens, make the following changes: + +* The STS version must be changed in the apps' registrations in the Azure portal. Set the value of `requestedAccessTokenVersion` to `2` in the apps' manifests, both in the app's registration and the web API's (`MinimalApiJwt`) registration. +* Use the V2 authority URL endpoint (example: `https://login.microsoftonline.com/{TENANT ID}/v2.0`, where the `{TENANT ID}` placeholder is the tenant ID). +* In the web API (`MinimalApiJwt`), explicitly validate the issuer: + + ```csharp + jwtOptions.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + // Ensure the issuer ends with /v2.0 if using the V2 endpoint + ValidIssuer = "https://login.microsoftonline.com/{TENANT ID}/v2.0", + ValidateAudience = true, + ValidAudiences = new[] { "{WEB API CLIENT ID 1}", "{WEB API CLIENT ID 2}", ... }, + ValidateLifetime = true + }; + ``` + + Instead of setting the property, you can specify a single valid audience with : + + ```csharp + ValidAudience = "{WEB API CLIENT ID}", + ``` + + The `{WEB API CLIENT ID}` placeholders in the preceding examples are ***only the client IDs***, not the full values passed to the `Audience` property. + + To supply a collection of valid audiences in an app that [configures Identity from app settings](#supply-configuration-with-the-json-configuration-provider-app-settings), you can use the following code to obtain the valid audiences configuration: + + ```csharp + ValidAudiences = builder.Configuration.GetSection( + "Authentication:Schemes:Bearer:ValidAudiences").Get(), + ``` + +For more information, see [Access tokens in the Microsoft identity platform: Token formats](/entra/identity-platform/access-tokens#token-formats). + ## Troubleshoot [!INCLUDE[](~/blazor/security/includes/troubleshoot-server.md)] diff --git a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md index 81e79bf26364..94b57fe3b2bf 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md @@ -178,9 +178,11 @@ The format of the Authority depends on the type of tenant in use. The following ME-ID tenant Authority example: ```csharp -jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee"; +jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/"; ``` +The preceding example uses the V1 STS token URL format. For guidance on V2 STS tokens, see . + AAD B2C tenant Authority example: ```csharp @@ -526,9 +528,11 @@ The format of the Authority depends on the type of tenant in use. The following ME-ID tenant Authority example: ```csharp -jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee"; +jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/"; ``` +The preceding example uses the V1 STS token URL format. For guidance on V2 STS tokens, see . + AAD B2C tenant Authority example: ```csharp @@ -873,9 +877,11 @@ The format of the Authority depends on the type of tenant in use. The following ME-ID tenant Authority example: ```csharp -jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee"; +jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/"; ``` +The preceding example uses the V1 STS token URL format. For guidance on V2 STS tokens, see . + AAD B2C tenant Authority example: ```csharp @@ -1193,13 +1199,15 @@ In the `MinimalApiJwt` project, add the following app settings configuration to "Authentication": { "Schemes": { "Bearer": { - "Authority": "https://sts.windows.net/{TENANT ID (WEB API)}", + "Authority": "https://sts.windows.net/{TENANT ID (WEB API)}/", "ValidAudiences": [ "{APP ID URI (WEB API)}" ] } } }, ``` +The preceding example uses the V1 STS token URL format. For guidance on V2 STS tokens, see . + Update the placeholders in the preceding configuration to match the values that the app uses in the `Program` file: * `{TENANT ID (WEB API)}`: The Tenant Id of the web API. @@ -1207,10 +1215,12 @@ Update the placeholders in the preceding configuration to match the values that Authority formats adopt the following patterns: -* ME-ID tenant type: `https://sts.windows.net/{TENANT ID}` +* ME-ID tenant type: `https://sts.windows.net/{TENANT ID}/` * Microsoft Entra External ID: `https://{DIRECTORY NAME}.ciamlogin.com/{TENANT ID}/v2.0` * B2C tenant type: `https://login.microsoftonline.com/{TENANT ID}/v2.0` +The preceding example for the ME-ID tenant type uses the V1 STS token URL format. For guidance on V2 STS tokens, see . + Audience formats adopt the following patterns (`{CLIENT ID}` is the Client Id of the web API; `{DIRECTORY NAME}` is the directory name, for example, `contoso`): * ME-ID tenant type: `api://{CLIENT ID}` diff --git a/aspnetcore/blazor/security/includes/troubleshoot-server.md b/aspnetcore/blazor/security/includes/troubleshoot-server.md index 6c22bdd334fc..ec18ab45b4f0 100644 --- a/aspnetcore/blazor/security/includes/troubleshoot-server.md +++ b/aspnetcore/blazor/security/includes/troubleshoot-server.md @@ -158,3 +158,51 @@ The following `UserClaims` component can be used directly in apps or serve as th } } ``` + +### Inspect the access token + +Obtaining the access token during development is often helpful when troubleshooting app and Azure configuration problems. In the following example for a weather forecast endpoint, the bearer token and token details are logged only when the app is compiled with the `DEBUG` symbol, which is typically a Debug build. You can decode the token using an online JWT token decoder, such as the [Microsoft JWT token decoder](https://jwt.ms/), or log details from the token in C#, as the following example demonstrates. + +> [!CAUTION] +> In production, avoid logging the token or its contents. + +```csharp +app.MapGet("/weather-forecast", (HttpContext context, ILogger logger) => +{ +#if DEBUG + var authHeader = context.Request.Headers.Authorization.FirstOrDefault(v => + v != null && + v.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)); + + if (authHeader is not null) + { + var token = authHeader["Bearer ".Length..].Trim(); + logger.LogDebug("Token: {Token}", token); + + try + { + var handler = + new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); + var jwtToken = handler.ReadJwtToken(token); + logger.LogDebug("Audience: {Audience}", + string.Join(", ", jwtToken.Audiences)); + logger.LogDebug("Issuer: {Issuer}", jwtToken.Issuer); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to decode token."); + } + } +#endif + + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}).RequireAuthorization(); +```