Skip to content

Commit 40b4189

Browse files
authored
(Blazor Wasm) Fixes duplication of sections in security docs, broken links and typos (#18501)
1 parent 511db03 commit 40b4189

File tree

1 file changed

+32
-226
lines changed

1 file changed

+32
-226
lines changed

aspnetcore/security/blazor/webassembly/hosted-with-identity-server.md

Lines changed: 32 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ Run the app from the Server project. When using Visual Studio, either:
230230

231231
## Name and role claim with API authorization
232232

233-
Identity Server can be configured to send `name` and `role` claims for authenticated users.
233+
### Custom user factory
234234

235235
In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single `role` claim. A single role is sent as a string value in the claim. The factory creates an individual `role` claim for each of the user's roles.
236236

@@ -298,252 +298,56 @@ In the Client app, register the factory in `Program.Main` (*Program.cs*):
298298

299299
```csharp
300300
builder.Services.AddApiAuthorization()
301-
.AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();
301+
.AddAccountClaimsPrincipalFactory<CustomUserFactory>();
302302
```
303303

304-
* In the Server app, call <xref:Microsoft.AspNetCore.Identity.IdentityBuilder.AddRoles*> on the Identity builder, which adds role-related services:
305-
306-
```csharp
307-
using Microsoft.AspNetCore.Identity;
308-
309-
...
310-
311-
services.AddDefaultIdentity<ApplicationUser>(options =>
312-
options.SignIn.RequireConfirmedAccount = true)
313-
.AddRoles<IdentityRole>()
314-
.AddEntityFrameworkStores<ApplicationDbContext>();
315-
```
316-
317-
* In the Server app:
318-
319-
* Configure Identity Server to put the `name` and `role` claims into the ID token and access token.
320-
* Prevent the default mapping for roles in the JWT token handler.
321-
322-
```csharp
323-
using System.IdentityModel.Tokens.Jwt;
324-
using System.Linq;
325-
326-
...
327-
328-
services.AddIdentityServer()
329-
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
330-
options.IdentityResources["openid"].UserClaims.Add("name");
331-
options.ApiResources.Single().UserClaims.Add("name");
332-
options.IdentityResources["openid"].UserClaims.Add("role");
333-
options.ApiResources.Single().UserClaims.Add("role");
334-
});
335-
336-
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
337-
```
338-
339-
Component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:
340-
341-
* [AuthorizeView component](xref:security/blazor/index#authorizeview-component) (Example: `<AuthorizeView Roles="admin">`)
342-
* [`[Authorize]`] attribute directive](xref:security/blazor/index#authorize-attribute) (<xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute>) (Example: `@attribute [Authorize(Roles = "admin")]`)
343-
* [Procedural logic](xref:security/blazor/index#procedural-logic) (Example: `if (user.IsInRole("admin")) { ... }`)
344-
345-
Multiple role tests are supported:
346-
347-
```csharp
348-
if (user.IsInRole("admin") && user.IsInRole("developer"))
349-
{
350-
...
351-
}
352-
```
353-
354-
`User.Identity.Name` is populated in the Client app with the user's username, which is usually their sign-in email address.
355-
356-
## Profile Service
357-
358-
In the Server app, create a `ProfileService` implementation. The Profile Service example in this section creates `name` and `role` claims for users similar to the scenario shown in the [Name and role claim with API authorization](#name-and-role-claim-with-api-authorization) section. The value of the `role` claim represents the user's assigned roles.
359-
360-
*ProfileService.cs*:
361-
362-
```csharp
363-
using IdentityModel;
364-
using IdentityServer4.Models;
365-
using IdentityServer4.Services;
366-
using System.Threading.Tasks;
367-
368-
public class ProfileService : IProfileService
369-
{
370-
public ProfileService()
371-
{
372-
}
373-
374-
public Task GetProfileDataAsync(ProfileDataRequestContext context)
375-
{
376-
var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
377-
context.IssuedClaims.AddRange(nameClaim);
378-
379-
var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
380-
context.IssuedClaims.AddRange(roleClaims);
381-
382-
return Task.CompletedTask;
383-
}
384-
385-
public Task IsActiveAsync(IsActiveContext context)
386-
{
387-
return Task.CompletedTask;
388-
}
389-
}
390-
```
391-
392-
In the Server app, register the Profile Service in `Startup.ConfigureServices`:
304+
In the Server app, call <xref:Microsoft.AspNetCore.Identity.IdentityBuilder.AddRoles*> on the Identity builder, which adds role-related services:
393305

394306
```csharp
395-
using IdentityServer4.Services;
307+
using Microsoft.AspNetCore.Identity;
396308

397309
...
398310

399-
services.AddTransient<IProfileService, ProfileService>();
311+
services.AddDefaultIdentity<ApplicationUser>(options =>
312+
options.SignIn.RequireConfirmedAccount = true)
313+
.AddRoles<IdentityRole>()
314+
.AddEntityFrameworkStores<ApplicationDbContext>();
400315
```
401316

402-
Component authorization approaches are functional at this point. Any of the authorization mechanisms in components can a role to authorize the user:
317+
### Configure Identity Server
403318

404-
* [AuthorizeView component](xref:security/blazor/index#authorizeview-component) (Example: `<AuthorizeView Roles="admin">`)
405-
* [`[Authorize]`] attribute directive](xref:security/blazor/index#authorize-attribute) (<xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute>) (Example: `@attribute [Authorize(Roles = "admin")]`)
406-
* [Procedural logic](xref:security/blazor/index#procedural-logic) (Example: `if (user.IsInRole("admin")) { ... }`)
319+
Use **one** of the following approaches:
407320

408-
Multiple role tests are supported:
321+
* [API authorization options](#api-authorization-options)
322+
* [Profile Service](#profile-service)
409323

410-
```csharp
411-
if (user.IsInRole("admin") && user.IsInRole("developer"))
412-
{
413-
...
414-
}
415-
```
324+
#### API authorization options
416325

417-
`User.Identity.Name` is populated in the Client app with the user's user name, which is usually their sign-in email address.
418-
419-
## Name and role claim with API authorization
420-
421-
Identity Server can be configured to send `name` and `role` claims for authenticated users.
422-
423-
In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single `role` claim. A single role is sent as a string value in the claim. The factory creates an individual `role` claim for each of the user's roles.
326+
In the Server app:
424327

425-
*CustomUserFactory.cs*:
328+
* Configure Identity Server to put the `name` and `role` claims into the ID token and access token.
329+
* Prevent the default mapping for roles in the JWT token handler.
426330

427331
```csharp
332+
using System.IdentityModel.Tokens.Jwt;
428333
using System.Linq;
429-
using System.Security.Claims;
430-
using System.Text.Json;
431-
using System.Threading.Tasks;
432-
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
433-
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
434-
435-
public class CustomUserFactory
436-
: AccountClaimsPrincipalFactory<RemoteUserAccount>
437-
{
438-
public CustomUserFactory(IAccessTokenProviderAccessor accessor)
439-
: base(accessor)
440-
{
441-
}
442-
443-
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
444-
RemoteUserAccount account,
445-
RemoteAuthenticationUserOptions options)
446-
{
447-
var user = await base.CreateUserAsync(account, options);
448-
449-
if (user.Identity.IsAuthenticated)
450-
{
451-
var identity = (ClaimsIdentity)user.Identity;
452-
var roleClaims = identity.FindAll(identity.RoleClaimType);
453-
454-
if (roleClaims != null && roleClaims.Any())
455-
{
456-
foreach (var existingClaim in roleClaims)
457-
{
458-
identity.RemoveClaim(existingClaim);
459-
}
460-
461-
var rolesElem = account.AdditionalProperties[identity.RoleClaimType];
462-
463-
if (rolesElem is JsonElement roles)
464-
{
465-
if (roles.ValueKind == JsonValueKind.Array)
466-
{
467-
foreach (var role in roles.EnumerateArray())
468-
{
469-
identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
470-
}
471-
}
472-
else
473-
{
474-
identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
475-
}
476-
}
477-
}
478-
}
479334

480-
return user;
481-
}
482-
}
483-
```
335+
...
484336

485-
In the Client app, register the factory in `Program.Main` (*Program.cs*):
337+
services.AddIdentityServer()
338+
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
339+
options.IdentityResources["openid"].UserClaims.Add("name");
340+
options.ApiResources.Single().UserClaims.Add("name");
341+
options.IdentityResources["openid"].UserClaims.Add("role");
342+
options.ApiResources.Single().UserClaims.Add("role");
343+
});
486344

487-
```csharp
488-
builder.Services.AddApiAuthorization()
489-
.AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();
345+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
490346
```
491347

492-
* In the Server app, call <xref:Microsoft.AspNetCore.Identity.IdentityBuilder.AddRoles*> on the Identity builder, which adds role-related services:
493-
494-
```csharp
495-
using Microsoft.AspNetCore.Identity;
496-
497-
...
498-
499-
services.AddDefaultIdentity<ApplicationUser>(options =>
500-
options.SignIn.RequireConfirmedAccount = true)
501-
.AddRoles<IdentityRole>()
502-
.AddEntityFrameworkStores<ApplicationDbContext>();
503-
```
504-
505-
* In the Server app:
506-
507-
* Configure Identity Server to put the `name` and `role` claims into the ID token and access token.
508-
* Prevent the default mapping for roles in the JWT token handler.
509-
510-
```csharp
511-
using System.IdentityModel.Tokens.Jwt;
512-
using System.Linq;
513-
514-
...
515-
516-
services.AddIdentityServer()
517-
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
518-
options.IdentityResources["openid"].UserClaims.Add("name");
519-
options.ApiResources.Single().UserClaims.Add("name");
520-
options.IdentityResources["openid"].UserClaims.Add("role");
521-
options.ApiResources.Single().UserClaims.Add("role");
522-
});
348+
#### Profile Service
523349

524-
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
525-
```
526-
527-
Component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:
528-
529-
* [AuthorizeView component](xref:security/blazor/index#authorizeview-component) (Example: `<AuthorizeView Roles="admin">`)
530-
* [`[Authorize]`] attribute directive](xref:security/blazor/index#authorize-attribute) (<xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute>) (Example: `@attribute [Authorize(Roles = "admin")]`)
531-
* [Procedural logic](xref:security/blazor/index#procedural-logic) (Example: `if (user.IsInRole("admin")) { ... }`)
532-
533-
Multiple role tests are supported:
534-
535-
```csharp
536-
if (user.IsInRole("admin") && user.IsInRole("developer"))
537-
{
538-
...
539-
}
540-
```
541-
542-
`User.Identity.Name` is populated in the Client app with the user's username, which is usually their sign-in email address.
543-
544-
## Profile Service
545-
546-
In the Server app, create a `ProfileService` implementation. The Profile Service example in this section creates `name` and `role` claims for users similar to the scenario shown in the [Name and role claim with API authorization](#name-and-role-claim-with-api-authorization) section. The value of the `role` claim represents the user's assigned roles.
350+
In the Server app, create a `ProfileService` implementation.
547351

548352
*ProfileService.cs*:
549353

@@ -587,10 +391,12 @@ using IdentityServer4.Services;
587391
services.AddTransient<IProfileService, ProfileService>();
588392
```
589393

590-
Component authorization approaches are functional at this point. Any of the authorization mechanisms in components can a role to authorize the user:
394+
### Use authorization mechanisms
395+
396+
In the Client app, component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:
591397

592398
* [AuthorizeView component](xref:security/blazor/index#authorizeview-component) (Example: `<AuthorizeView Roles="admin">`)
593-
* [`[Authorize]`] attribute directive](xref:security/blazor/index#authorize-attribute) (<xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute>) (Example: `@attribute [Authorize(Roles = "admin")]`)
399+
* [`[Authorize]` attribute directive](xref:security/blazor/index#authorize-attribute) (<xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute>) (Example: `@attribute [Authorize(Roles = "admin")]`)
594400
* [Procedural logic](xref:security/blazor/index#procedural-logic) (Example: `if (user.IsInRole("admin")) { ... }`)
595401

596402
Multiple role tests are supported:

0 commit comments

Comments
 (0)