Blazor Fullstack–Part 4

About

App Roles

Open the App Manifest.
AppRoles

To create roles such as  (Reader, Writer, SuperUser) add the following objects to the appRoles array.

"appRoles": [
	{
		"allowedMemberTypes": [
			"User"
		],
		"description": "Reader all data.",
		"displayName": "Reader",
		"id": "254a1feb-715d-4a0f-9dfd-fef415381051",
		"isEnabled": true,
		"lang": null,
		"origin": "Application",
		"value": "Reader"
	},
	{
		"allowedMemberTypes": [
			"User"
		],
		"description": "Read/Write all data",
		"displayName": "Writer",
		"id": "cbd3dbc4-0db7-4a9e-a26f-b3588e798daf",
		"isEnabled": true,
		"lang": null,
		"origin": "Application",
		"value": "Writer"
	},
	{
		"allowedMemberTypes": [
			"User"
		],
		"description": "Super Users can see something special",
		"displayName": "SuperUser",
		"id": "b7d5415e-2ff2-48bb-a619-45a6a70dc733",
		"isEnabled": true,
		"lang": null,
		"origin": "Application",
		"value": "SuperUser"
	}
	],

NOTE: the guids need to be unique.  Create them in Powershell or Package Manager Console by running: [guid]::NewGuid()
NewGuid

Assign Roles to Users

In the Default Directory go to Enterprise applications. Find the app in the list (Sample Blazor), then in “Users and groups” add users and roles as required.

UsersToRoles

Blazor Client Roles

In the client app create a new Folder called “Auth” and create a class called “SampleBlazorUserAccount”.

public class SampleBlazorUserAccount : RemoteUserAccount
{
	[JsonPropertyName("groups")]
	public string[] Groups { get; set; } = new string[] { };

	[JsonPropertyName("roles")]
	public string[] Roles { get; set; } = new string[] { };
}

This is very similar to here but includes initializing with blank arrays. (I had trouble with this because I was using the free Azure off my microsoft account and that doesn’t allow use of roles)

Create the factory class in the same folder.

public class SampleBlazorUserFactory : AccountClaimsPrincipalFactory<SampleBlazorUserAccount>
{
	public SampleBlazorUserFactory(NavigationManager navigationManager,
		IAccessTokenProviderAccessor accessor)
		: base(accessor)
	{

	}

	public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
		SampleBlazorUserAccount account,
		RemoteAuthenticationUserOptions options)
	{
		var initialUser = await base.CreateUserAsync(account, options);

		Console.WriteLine($"CreateUserAsync() initialUser.Identity.IsAuthenticated: {initialUser.Identity.IsAuthenticated}");

		if (initialUser.Identity.IsAuthenticated)
		{
			var userIdentity = (ClaimsIdentity)initialUser.Identity;

				foreach (var role in account.Roles)
				{
					userIdentity.AddClaim(new Claim("role", role));
				}

				foreach (var group in account.Groups)
				{
					userIdentity.AddClaim(new Claim("group", group));
				} 
		}

		return initialUser;
	}
}

Fix up the missing using statements with Ctrl+.

Register the factory in Program..Main by updating the following lines.
Register_the_user_Factory

For the sake of showing an authorization example create another Get method on WeatherForecastController to return something for the SuperUsers. Copy the Get method and change the following code.
GetSpecial_Forecast

NOTE: The roles string can be refactored into a class in the Shared project. like so:
AuthRoles

public static class AuthRoles
    {
        public const string Reader = "Reader";
        public const string Writer = "Writer";
        public const string SuperUser = "SuperUser";
    }

In which case the Authorize attribute would be replaced with
AuthRoles1

Client UI

In the FetchData.razor file of the client project add an AuthorizeView section.

<AuthorizeView Roles="@AuthRoles.SuperUser">
    <Authorized>
        <button class="btn btn-outline-primary" @onclick="GetSpecialForecast">Get Special Forecast</button>
    </Authorized>
</AuthorizeView>

and in the code section write the button handler

private async Task GetSpecialForecast()
{
	forecasts = null;
	StateHasChanged();

	try
	{
		forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast/GetSpecial");
	}
	catch (AccessTokenNotAvailableException exception)
	{
		exception.Redirect();
	}
}

Run the program and if the logged on user is a Super User then they will get a special button to load the summaries with the exclamation marks.