Skip to content

Commit fd7e98f

Browse files
authored
Convert configuration of BWA+Entra samples (#35387)
1 parent faa1dba commit fd7e98f

File tree

2 files changed

+217
-74
lines changed

2 files changed

+217
-74
lines changed

aspnetcore/blazor/security/blazor-web-app-with-entra.md

Lines changed: 216 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ The <xref:Microsoft.AspNetCore.Builder.AuthorizationEndpointConventionBuilderExt
9696

9797
## Configure the backend web API project (`MinimalApiJwt`)
9898

99-
Configure the project in the <xref:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions> of the <xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A> call in the project's `Program` file.
99+
Configure the project in the <xref:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions> of the <xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A> call in the `MinimalApiJwt` project's `Program` file.
100100

101101
For the web API app's registration, the `Weather.Get` scope is configured in the Entra or Azure portal in **Expose an API**.
102102

@@ -144,51 +144,62 @@ jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4
144144

145145
## Configure the server project (`BlazorWebAppEntra`)
146146

147-
<xref:Microsoft.Identity.Web.AppBuilderExtension.AddMicrosoftIdentityWebApp%2A> from [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) ([`Microsoft.Identity.Web` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web), [API documentation](<xref:Microsoft.Identity.Web?displayProperty=fullName>)) is configured by the `AzureAd` section of the server project's `appsettings.json` file.
147+
<xref:Microsoft.Identity.Web.AppBuilderExtension.AddMicrosoftIdentityWebApp%2A> from [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) ([`Microsoft.Identity.Web` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web), [API documentation](<xref:Microsoft.Identity.Web?displayProperty=fullName>)) is configured in the `BlazorWebAppEntra` project's `Program` file.
148148

149-
Obtain the application (client) ID, tenant (publisher) domain, and directory (tenant) ID from the app's registration in the Entra or Azure portal. The App ID URI is obtained for the `Weather.Get` scope. Don't include the scope name, and there's no trailing slash.
149+
Obtain the application (client) ID, tenant (publisher) domain, and directory (tenant) ID from the app's registration in the Entra or Azure portal. The App ID URI is obtained for the `Weather.Get` scope from the web API's registration. Don't include the scope name when taking the App ID URI from the portal.
150150

151-
```json
152-
"AzureAd": {
153-
"CallbackPath": "/signin-oidc",
154-
"ClientId": "{CLIENT ID}",
155-
"Domain": "{TENANT DOMAIN}",
156-
"Instance": "https://login.microsoftonline.com/",
157-
"ResponseType": "code",
158-
"TenantId": "{TENANT ID}"
159-
},
160-
...
161-
"DownstreamApi": {
162-
"BaseUrl": "{BASE ADDRESS}",
163-
"Scopes": [ "{APP ID URI}/{SCOPE NAME}" ]
164-
}
151+
In the `BlazorWebAppEntra` project's `Program` file, provide the values for the following placeholders in Microsoft Identity Web configuration:
152+
153+
```csharp
154+
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
155+
.AddMicrosoftIdentityWebApp(msIdentityOptions =>
156+
{
157+
msIdentityOptions.CallbackPath = "/signin-oidc";
158+
msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
159+
msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
160+
msIdentityOptions.Instance = "https://login.microsoftonline.com/";
161+
msIdentityOptions.ResponseType = "code";
162+
msIdentityOptions.TenantId = "{TENANT ID}";
163+
})
164+
.EnableTokenAcquisitionToCallDownstreamApi()
165+
.AddDownstreamApi("DownstreamApi", configOptions =>
166+
{
167+
configOptions.BaseUrl = "{BASE ADDRESS}";
168+
configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ];
169+
})
170+
.AddInMemoryTokenCaches();
165171
```
166172

167-
Placeholders in the preceding example:
173+
Placeholders in the preceding configuration:
168174

169-
* `{CLIENT ID}`: The application (client) ID.
170-
* `{TENANT DOMAIN}`: The tenant (publisher) domain.
175+
* `{CLIENT ID (BLAZOR APP)}`: The application (client) ID.
176+
* `{DIRECTORY NAME}`: The directory name of the tenant (publisher) domain.
171177
* `{TENANT ID}`: The directory (tenant) ID.
172178
* `{BASE ADDRESS}`: The web API's base address.
173-
* `{APP ID URI}`: The App ID URI for web API scopes.
174-
* `{SCOPE NAME}`: A scope name.
179+
* `{APP ID URI}`: The App ID URI for web API scopes. Either of the following formats are used, where the `{CLIENT ID (WEB API)}` placeholder is the Client Id of the web API's Entra registration, and the `{DIRECTORY NAME}` placeholder is the directory name of the tenant (publishers) domain (example: `contoso`).
180+
* ME-ID tenant format: `api://{CLIENT ID (WEB API)}`
181+
* B2C tenant format: `https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}`
175182

176183
Example:
177184

178-
```json
179-
"AzureAd": {
180-
"CallbackPath": "/signin-oidc",
181-
"ClientId": "00001111-aaaa-2222-bbbb-3333cccc4444",
182-
"Domain": "contoso.onmicrosoft.com",
183-
"Instance": "https://login.microsoftonline.com/",
184-
"ResponseType": "code",
185-
"TenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
186-
},
187-
...
188-
"DownstreamApi": {
189-
"BaseUrl": "https://localhost:7277",
190-
"Scopes": [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ]
191-
}
185+
```csharp
186+
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
187+
.AddMicrosoftIdentityWebApp(msIdentityOptions =>
188+
{
189+
msIdentityOptions.CallbackPath = "/signin-oidc";
190+
msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
191+
msIdentityOptions.Domain = "contoso.onmicrosoft.com";
192+
msIdentityOptions.Instance = "https://login.microsoftonline.com/";
193+
msIdentityOptions.ResponseType = "code";
194+
msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
195+
})
196+
.EnableTokenAcquisitionToCallDownstreamApi()
197+
.AddDownstreamApi("DownstreamApi", configOptions =>
198+
{
199+
configOptions.BaseUrl = "https://localhost:7277";
200+
configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ];
201+
})
202+
.AddInMemoryTokenCaches();
192203
```
193204

194205
:::zone-end
@@ -293,7 +304,7 @@ The <xref:Microsoft.AspNetCore.Builder.AuthorizationEndpointConventionBuilderExt
293304

294305
## Configure the backend web API project (`MinimalApiJwt`)
295306

296-
Configure the project in the <xref:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions> of the <xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A> call in the project's `Program` file.
307+
Configure the `MinimalApiJwt` project in the <xref:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions> of the <xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A> call in the project's `Program` file.
297308

298309
For the web API app's registration, the `Weather.Get` scope is configured in the Entra or Azure portal in **Expose an API**.
299310

@@ -341,58 +352,69 @@ jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4
341352

342353
## Configure the server project (`BlazorWebAppEntra`)
343354

344-
<xref:Microsoft.Identity.Web.AppBuilderExtension.AddMicrosoftIdentityWebApp%2A> from [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) ([`Microsoft.Identity.Web` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web), [API documentation](<xref:Microsoft.Identity.Web?displayProperty=fullName>)) is configured by the `AzureAd` section of the server project's `appsettings.json` file.
355+
<xref:Microsoft.Identity.Web.AppBuilderExtension.AddMicrosoftIdentityWebApp%2A> from [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) ([`Microsoft.Identity.Web` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web), [API documentation](<xref:Microsoft.Identity.Web?displayProperty=fullName>)) is configured in the `BlazorWebAppEntra` project's `Program` file.
345356

346-
Obtain the application (client) ID, tenant (publisher) domain, and directory (tenant) ID from the app's registration in the Entra or Azure portal. The App ID URI is obtained for the `Weather.Get` scope. Don't include the scope name, and there's no trailing slash.
357+
Obtain the application (client) ID, tenant (publisher) domain, and directory (tenant) ID from the app's registration in the Entra or Azure portal. The App ID URI is obtained for the `Weather.Get` scope from the web API's registration. Don't include the scope name when taking the App ID URI from the portal.
347358

348-
```json
349-
"AzureAd": {
350-
"CallbackPath": "/signin-oidc",
351-
"ClientId": "{CLIENT ID}",
352-
"Domain": "{TENANT DOMAIN}",
353-
"Instance": "https://login.microsoftonline.com/",
354-
"ResponseType": "code",
355-
"TenantId": "{TENANT ID}"
356-
},
357-
...
358-
"DownstreamApi": {
359-
"BaseUrl": "{BASE ADDRESS}",
360-
"Scopes": [ "{APP ID URI}/{SCOPE}" ]
361-
}
359+
In the `BlazorWebAppEntra` project's `Program` file, provide the values for the following placeholders in Microsoft Identity Web configuration:
360+
361+
```csharp
362+
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
363+
.AddMicrosoftIdentityWebApp(msIdentityOptions =>
364+
{
365+
msIdentityOptions.CallbackPath = "/signin-oidc";
366+
msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
367+
msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
368+
msIdentityOptions.Instance = "https://login.microsoftonline.com/";
369+
msIdentityOptions.ResponseType = "code";
370+
msIdentityOptions.TenantId = "{TENANT ID}";
371+
})
372+
.EnableTokenAcquisitionToCallDownstreamApi()
373+
.AddDownstreamApi("DownstreamApi", configOptions =>
374+
{
375+
configOptions.BaseUrl = "{BASE ADDRESS}";
376+
configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ];
377+
})
378+
.AddInMemoryTokenCaches();
362379
```
363380

364-
Placeholders in the preceding example:
381+
Placeholders in the preceding configuration:
365382

366-
* `{CLIENT ID}`: The application (client) ID.
367-
* `{TENANT DOMAIN}`: The tenant (publisher) domain.
383+
* `{CLIENT ID (BLAZOR APP)}`: The application (client) ID.
384+
* `{DIRECTORY NAME}`: The directory name of the tenant (publisher) domain.
368385
* `{TENANT ID}`: The directory (tenant) ID.
369386
* `{BASE ADDRESS}`: The web API's base address.
370-
* `{APP ID URI}`: The App ID URI for web API scopes.
371-
* `{SCOPE NAME}`: A scope name.
387+
* `{APP ID URI}`: The App ID URI for web API scopes. Either of the following formats are used, where the `{CLIENT ID (WEB API)}` placeholder is the Client Id of the web API's Entra registration, and the `{DIRECTORY NAME}` placeholder is the directory name of the tenant (publishers) domain (example: `contoso`).
388+
* ME-ID tenant format: `api://{CLIENT ID (WEB API)}`
389+
* B2C tenant format: `https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}`
372390

373391
Example:
374392

375-
```json
376-
"AzureAd": {
377-
"CallbackPath": "/signin-oidc",
378-
"ClientId": "00001111-aaaa-2222-bbbb-3333cccc4444",
379-
"Domain": "contoso.onmicrosoft.com",
380-
"Instance": "https://login.microsoftonline.com/",
381-
"ResponseType": "code",
382-
"TenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
383-
},
384-
...
385-
"DownstreamApi": {
386-
"BaseUrl": "https://localhost:7277",
387-
"Scopes": [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ]
388-
}
393+
```csharp
394+
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
395+
.AddMicrosoftIdentityWebApp(msIdentityOptions =>
396+
{
397+
msIdentityOptions.CallbackPath = "/signin-oidc";
398+
msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
399+
msIdentityOptions.Domain = "contoso.onmicrosoft.com";
400+
msIdentityOptions.Instance = "https://login.microsoftonline.com/";
401+
msIdentityOptions.ResponseType = "code";
402+
msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
403+
})
404+
.EnableTokenAcquisitionToCallDownstreamApi()
405+
.AddDownstreamApi("DownstreamApi", configOptions =>
406+
{
407+
configOptions.BaseUrl = "https://localhost:7277";
408+
configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ];
409+
})
410+
.AddInMemoryTokenCaches();
389411
```
390412

391413
:::zone-end
392414

393415
The callback path (`CallbackPath`) must match the redirect URI (login callback path) configured when registering the application in the Entra or Azure portal. Paths are configured in the **Authentication** blade of the app's registration. The default value of `CallbackPath` is `/signin-oidc` for a registered redirect URI of `https://localhost/signin-oidc` (a port isn't required).
394416

395-
The <xref:Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutCallbackPath%2A> (configuration key: "`SignedOutCallbackPath`") is the request path within the app's base path intercepted by the OpenID Connect handler where the user agent is first returned after signing out from Entra. The sample app doesn't set a value for the path because the default value of "`/signout-callback-oidc`" is used. After intercepting the request, the OpenID Connect handler redirects to the <xref:Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutRedirectUri%2A> or <xref:Microsoft.AspNetCore.Authentication.AuthenticationProperties.RedirectUri%2A>, if specified.
417+
The <xref:Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutCallbackPath%2A> is the request path within the app's base path intercepted by the OpenID Connect handler where the user agent is first returned after signing out from Entra. The sample app doesn't set a value for the path because the default value of "`/signout-callback-oidc`" is used. After intercepting the request, the OpenID Connect handler redirects to the <xref:Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutRedirectUri%2A> or <xref:Microsoft.AspNetCore.Authentication.AuthenticationProperties.RedirectUri%2A>, if specified.
396418

397419
Configure the signed-out callback path in the app's Entra registration. In the Entra or Azure portal, set the path in the **Web** platform configuration's **Redirect URI** entries:
398420

@@ -538,6 +560,127 @@ In the `Program` file, all claims are serialized by setting <xref:Microsoft.AspN
538560

539561
:::moniker-end
540562

563+
## Supply configuration with the JSON configuration provider (app settings)
564+
565+
The [sample solution projects](#sample-solution) configure Microsoft Identity Web and JWT bearer authentication in their `Program` files in order to make configuration settings discoverable using C# autocompletion. Professional apps usually use a *configuration provider* to configure OIDC options, such as the default [JSON configuration provider](xref:fundamentals/configuration/index). The JSON configuration provider loads configuration from app settings files `appsettings.json`/`appsettings.{ENVIRONMENT}.json`, where the `{ENVIRONMENT}` placeholder is the app's [runtime environment](xref:fundamentals/environments). Follow the guidance in this section to use app settings files for configuration.
566+
567+
In the app settings file (`appsettings.json`) of the `BlazorWebAppEntra` project, add the following JSON configuration:
568+
569+
```json
570+
{
571+
"AzureAd": {
572+
"CallbackPath": "/signin-oidc",
573+
"ClientId": "{CLIENT ID (BLAZOR APP)}",
574+
"Domain": "{DIRECTORY NAME}.onmicrosoft.com",
575+
"Instance": "https://login.microsoftonline.com/",
576+
"ResponseType": "code",
577+
"TenantId": "{TENANT ID}"
578+
},
579+
"DownstreamApi": {
580+
"BaseUrl": "{BASE ADDRESS}",
581+
"Scopes": [ "{APP ID URI}/Weather.Get" ]
582+
}
583+
}
584+
```
585+
586+
Update the placeholders in the preceding configuration to match the values that the app uses in the `Program` file:
587+
588+
* `{CLIENT ID (BLAZOR APP)}`: The application (client) ID.
589+
* `{DIRECTORY NAME}`: The directory name of the tenant (publisher) domain.
590+
* `{TENANT ID}`: The directory (tenant) ID.
591+
* `{BASE ADDRESS}`: The web API's base address.
592+
* `{APP ID URI}`: The App ID URI for web API scopes. Either of the following formats are used, where the `{CLIENT ID (WEB API)}` placeholder is the Client Id of the web API's Entra registration, and the `{DIRECTORY NAME}` placeholder is the directory name of the tenant (publishers) domain (example: `contoso`).
593+
* ME-ID tenant format: `api://{CLIENT ID (WEB API)}`
594+
* B2C tenant format: `https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}`
595+
596+
Example:
597+
598+
```json
599+
"AzureAd": {
600+
"CallbackPath": "/signin-oidc",
601+
"ClientId": "00001111-aaaa-2222-bbbb-3333cccc4444",
602+
"Domain": "contoso.onmicrosoft.com",
603+
"Instance": "https://login.microsoftonline.com/",
604+
"ResponseType": "code",
605+
"TenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
606+
},
607+
...
608+
"DownstreamApi": {
609+
"BaseUrl": "https://localhost:7277",
610+
"Scopes": [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ]
611+
}
612+
```
613+
614+
Update any other values in the preceding configuration to match custom/non-default values used in the `Program` file.
615+
616+
The configuration is automatically picked up by the authentication builder.
617+
618+
Make the following changes in the `Program` file:
619+
620+
```diff
621+
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
622+
- .AddMicrosoftIdentityWebApp(msIdentityOptions =>
623+
- {
624+
- msIdentityOptions.CallbackPath = "...";
625+
- msIdentityOptions.ClientId = "...";
626+
- msIdentityOptions.Domain = "...";
627+
- msIdentityOptions.Instance = "...";
628+
- msIdentityOptions.ResponseType = "...";
629+
- msIdentityOptions.TenantId = "...";
630+
- })
631+
+ .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
632+
.EnableTokenAcquisitionToCallDownstreamApi()
633+
- .AddDownstreamApi("DownstreamApi", configOptions =>
634+
- {
635+
- configOptions.BaseUrl = "...";
636+
- configOptions.Scopes = [ "..." ];
637+
- })
638+
+ .AddDownstreamApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
639+
.AddInMemoryTokenCaches();
640+
```
641+
642+
In the `MinimalApiJwt` project, add the following app settings configuration to the `appsettings.json` file:
643+
644+
```json
645+
"Authentication": {
646+
"Schemes": {
647+
"Bearer": {
648+
"Authority": "https://sts.windows.net/{TENANT ID (WEB API)}/",
649+
"ValidAudiences": [ "{APP ID URI (WEB API)}" ]
650+
}
651+
}
652+
},
653+
```
654+
655+
Update the placeholders in the preceding configuration to match the values that the app uses in the `Program` file:
656+
657+
* `{TENANT ID (WEB API)}`: The Tenant Id of the web API.
658+
* `{APP ID URI (WEB API)}`: The App ID URI of the web API.
659+
660+
Authority formats adopt the following patterns:
661+
662+
* ME-ID tenant type: `https://sts.windows.net/{TENANT ID}/`
663+
* B2C tenant type: `https://login.microsoftonline.com/{TENANT ID}/v2.0/`
664+
665+
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`):
666+
667+
* ME-ID tenant type: `api://{CLIENT ID}`
668+
* B2C tenant type: `https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}`
669+
670+
The configuration is automatically picked up by the JWT bearer authentication builder.
671+
672+
Remove the following lines from the `Program` file:
673+
674+
```diff
675+
- jwtOptions.Authority = "...";
676+
- jwtOptions.Audience = "...";
677+
```
678+
679+
For more information on configuration, see the following resources:
680+
681+
* <xref:fundamentals/configuration/index>
682+
* <xref:blazor/fundamentals/configuration>
683+
541684
## Redirect to the home page on logout
542685

543686
The `LogInOrOut` component (`Layout/LogInOrOut.razor`) sets a hidden field for the return URL (`ReturnUrl`) to the current URL (`currentURL`). When the user signs out of the app, the identity provider returns the user to the page from which they logged out. If the user logs out from a secure page, they're returned to the same secure page and sent back through the authentication process. This authentication flow is reasonable when users need to change accounts regularly.

0 commit comments

Comments
 (0)