@@ -5,7 +5,7 @@ description: To create a new Blazor hosted app with authentication from within V
55monikerRange : ' >= aspnetcore-3.1'
66ms.author : riande
77ms.custom : mvc
8- ms.date : 05/11 /2020
8+ ms.date : 05/19 /2020
99no-loc : [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010uid : security/blazor/webassembly/hosted-with-identity-server
1111---
@@ -234,6 +234,194 @@ Run the app from the Server project. When using Visual Studio, either:
234234* Set the ** Startup Projects ** drop down list in the toolbar to the * Server API app * and select the ** Run ** button .
235235* Select the Server project in ** Solution Explorer ** and select the ** Run ** button in the toolbar or start the app from the ** Debug ** menu .
236236
237+ ## Name and role claim with API authorization
238+
239+ Identity Server can be configured to send `name ` and `role ` claims for authenticated users .
240+
241+ 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 .
242+
243+ *CustomUserFactory .cs *:
244+
245+ ```csharp
246+ using System .Linq ;
247+ using System .Security .Claims ;
248+ using System .Text .Json ;
249+ using System .Threading .Tasks ;
250+ using Microsoft .AspNetCore .Components .WebAssembly .Authentication ;
251+ using Microsoft .AspNetCore .Components .WebAssembly .Authentication .Internal ;
252+
253+ public class CustomUserFactory
254+ : AccountClaimsPrincipalFactory < RemoteUserAccount >
255+ {
256+ public CustomUserFactory (IAccessTokenProviderAccessor accessor )
257+ : base (accessor )
258+ {
259+ }
260+
261+ public async override ValueTask < ClaimsPrincipal > CreateUserAsync (
262+ RemoteUserAccount account ,
263+ RemoteAuthenticationUserOptions options )
264+ {
265+ var user = await base .CreateUserAsync (account , options );
266+
267+ if (user .Identity .IsAuthenticated )
268+ {
269+ var identity = (ClaimsIdentity )user .Identity ;
270+ var roleClaims = identity .FindAll (identity .RoleClaimType );
271+
272+ if (roleClaims != null && roleClaims .Any ())
273+ {
274+ foreach (var existingClaim in roleClaims )
275+ {
276+ identity .RemoveClaim (existingClaim );
277+ }
278+
279+ var rolesElem = account .AdditionalProperties [identity .RoleClaimType ];
280+
281+ if (rolesElem is JsonElement roles )
282+ {
283+ if (roles .ValueKind == JsonValueKind .Array )
284+ {
285+ foreach (var role in roles .EnumerateArray ())
286+ {
287+ identity .AddClaim (new Claim (options .RoleClaim , role .GetString ()));
288+ }
289+ }
290+ else
291+ {
292+ identity .AddClaim (new Claim (options .RoleClaim , roles .GetString ()));
293+ }
294+ }
295+ }
296+ }
297+
298+ return user ;
299+ }
300+ }
301+ ```
302+
303+ In the Client app , register the factory in `Program .Main ` (* Program .cs * ):
304+
305+ ```csharp
306+ builder .Services .AddApiAuthorization ()
307+ .AddAccountClaimsPrincipalFactory <RolesClaimsPrincipalFactory >();
308+ ```
309+
310+ * In the Server app , call < xref :Microsoft .AspNetCore .Identity .IdentityBuilder .AddRoles * > on the Identity builder , which adds role - related services :
311+
312+ ```csharp
313+ using Microsoft .AspNetCore .Identity ;
314+
315+ .. .
316+
317+ services .AddDefaultIdentity <ApplicationUser >(options =>
318+ options .SignIn .RequireConfirmedAccount = true )
319+ .AddRoles <IdentityRole >()
320+ .AddEntityFrameworkStores <ApplicationDbContext >();
321+ ```
322+
323+ * In the Server app :
324+
325+ * Configure Identity Server to put the `name ` and `role ` claims into the ID token and access token .
326+ * Prevent the default mapping for roles in the JWT token handler .
327+
328+ ```csharp
329+ using System .IdentityModel .Tokens .Jwt ;
330+ using System .Linq ;
331+
332+ .. .
333+
334+ services .AddIdentityServer ()
335+ .AddApiAuthorization <ApplicationUser , ApplicationDbContext >(options => {
336+ options .IdentityResources [" openid" ].UserClaims .Add (" name" );
337+ options .ApiResources .Single ().UserClaims .Add (" name" );
338+ options .IdentityResources [" openid" ].UserClaims .Add (" role" );
339+ options .ApiResources .Single ().UserClaims .Add (" role" );
340+ });
341+
342+ JwtSecurityTokenHandler .DefaultInboundClaimTypeMap .Remove (" role" );
343+ ```
344+
345+ Component authorization approaches are functional at this point . Any of the authorization mechanisms in components can use a role to authorize the user :
346+
347+ * [AuthorizeView component ](xref :security / blazor / index #authorizeview - component ) (Example : `< AuthorizeView Roles = " admin" > `)
348+ * [`[Authorize ]` attribute directive ](xref :security / blazor / index #authorize - attribute ) (Example : `@attribute [Authorize (Roles = " admin" )]`)
349+ * [Procedural logic ](xref :security / blazor / index #procedural - logic ) (Example : `if (user .IsInRole (" admin" )) { .. . }`)
350+
351+ Multiple role tests are supported :
352+
353+ ```csharp
354+ if (user .IsInRole (" admin" ) && user .IsInRole (" developer" ))
355+ {
356+ .. .
357+ }
358+ ```
359+
360+ `User .Identity .Name ` is populated in the Client app with the user 's username , which is usually their sign -in email address .
361+
362+ ## Profile Service
363+
364+ 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.
365+
366+ * ProfileService .cs * :
367+
368+ ```csharp
369+ using IdentityModel ;
370+ using IdentityServer4 .Models ;
371+ using IdentityServer4 .Services ;
372+ using System .Threading .Tasks ;
373+
374+ public class ProfileService : IProfileService
375+ {
376+ public ProfileService ()
377+ {
378+ }
379+
380+ public Task GetProfileDataAsync (ProfileDataRequestContext context )
381+ {
382+ var nameClaim = context .Subject .FindAll (JwtClaimTypes .Name );
383+ context .IssuedClaims .AddRange (nameClaim );
384+
385+ var roleClaims = context .Subject .FindAll (JwtClaimTypes .Role );
386+ context .IssuedClaims .AddRange (roleClaims );
387+
388+ return Task .CompletedTask ;
389+ }
390+
391+ public Task IsActiveAsync (IsActiveContext context )
392+ {
393+ return Task .CompletedTask ;
394+ }
395+ }
396+ ```
397+
398+ In the Server app , register the Profile Service in `Startup .ConfigureServices `:
399+
400+ ```csharp
401+ using IdentityServer4 .Services ;
402+
403+ .. .
404+
405+ services .AddTransient <IProfileService , ProfileService >();
406+ ```
407+
408+ Component authorization approaches are functional at this point . Any of the authorization mechanisms in components can a role to authorize the user :
409+
410+ * [AuthorizeView component ](xref :security / blazor / index #authorizeview - component ) (Example : `< AuthorizeView Roles = " admin" > `)
411+ * [`[Authorize ]` attribute directive ](xref :security / blazor / index #authorize - attribute ) (Example : `@attribute [Authorize (Roles = " admin" )]`)
412+ * [Procedural logic ](xref :security / blazor / index #procedural - logic ) (Example : `if (user .IsInRole (" admin" )) { .. . }`)
413+
414+ Multiple role tests are supported :
415+
416+ ```csharp
417+ if (user .IsInRole (" admin" ) && user .IsInRole (" developer" ))
418+ {
419+ .. .
420+ }
421+ ```
422+
423+ `User .Identity .Name ` is populated in the Client app with the user 's user name , which is usually their sign -in email address .
424+
237425[!INCLUDE [](~ / includes / blazor - security / usermanager - signinmanager .md )]
238426
239427[! INCLUDE [](~ / includes / blazor - security / troubleshoot .md )]
0 commit comments