How do I detect navigation events in Blazor WebAssembly?
We can use NavigationManager, which has a built-in LocationChanged event that will be triggered when the user navigates away from the current page. In the following code, we use an alert JS function by adding the IJSRuntime to show the user has navigated by overriding the OnInitialized() method through the alert message. [Index.razor] When the user navigates from the home page to the counter page or vice versa, the alert message stating “User has navigated to other page” will be shown. View Sample in GitHub
How do I allow anonymous users on a specific Blazor page?
We generally use authentication for specific pages to prevent access by unauthorized users. In the case that we do want a specific page to be accessed by any users, not only authorized users, we will use the anonymous users category. To make a Blazor page accessible by all users, use [AllowAnonymous] with @attribute code in the _Imports.razor file in your app. [_Imports.razor] By doing this users can access the secured page (here we secured the fetch data) without a login.
How do I implement Windows authentication and authorization in Blazor WebAssembly?
As the Blazor client runs in the browser, both user authorization and authentication for WebAssembly will be completely handled by the back-end API.The back-end web API must handle the authorization on every API call, and it tells the Blazor app whether the user is authenticated and has resource access. It enables your Blazor app to show the correct context to the user. Create a Blazor WebAssembly app using Visual Studio 2019 with ASP.NET Core hosting enabled. Reorganize the folder names according to usage: Sample.Server is renamed to Sample.Api as we are going to use this as our API. Sample.Client is renamed to Sample.WebApp. Sample.Shared is renamed to Sample.Common. In the Sample.Common folder, create a Models folder, and move the generated WeatherForecast class to this folder and change its namespace to Sample.Models. Add the AuthorizedUser class in the Models folder under the Sample.Shared folder. namespace Sample.Common { public class AuthorizedUser { public string Name { get; set; } public string Roles { get; set; } } Add a new SettingsController file in the Sample.Api folder. using Sample.Models;using Microsoft.AspNetCore.Mvc; namespace Sample.Api.Controllers { [Route(“api/[controller]”)] [ApiController] public class SettingsController : ControllerBase { [HttpGet(“user”)] public AuthorizedUser GetUser() { return new AuthorizedUser(); // User signed in: //return new AuthorizedUser { Name = “UserName” }; } } } Prepare your Sample.WebApp Install the NuGet package Microsoft.AspNetCore.Components.Authorization. Add the installed package in the _Imports.razor file. Wrap the <CascadingAuthenticationState> in the App.razor file. <CascadingAuthenticationState> <Router AppAssembly=”@typeof(Program).Assembly”> … </Router> </CascadingAuthenticationState> Add a Service folder and add a ClientAuthorizationService class in it. using Sample.Models;using Microsoft.AspNetCore.Components;using Microsoft.AspNetCore.Components.Authorization; using System;using System.Collections.Generic;using System.Net.Http;using System.Security.Claims;using System.Threading.Tasks; namespace Sample.WebApp.Services { public class ClientAuthorizationService : AuthenticationStateProvider { private const string AuthenticationType = “BackEnd”; private readonly HttpClient _httpClient; public ClientAuthorizationService(HttpClient httpClient) { if (httpClient == null) throw new ArgumentNullException(nameof(httpClient)); _httpClient = httpClient; } public string ApiUriGetAuthorizedUser { get; set; } public string ApiUriSignIn { get; set; } public string ApiUriSignOut { get; set; } public AuthorizedUser AuthorizedUser { get; private set; } = new AuthorizedUser(); public override async Task<AuthenticationState> GetAuthenticationStateAsync() { ClaimsPrincipal user; if (!string.IsNullOrEmpty(ApiUriGetAuthorizedUser)) AuthorizedUser = await _httpClient.GetJsonAsync<AuthorizedUser>(ApiUriGetAuthorizedUser); if (string.IsNullOrEmpty(AuthorizedUser.Name)) { user = new ClaimsPrincipal(); } else { var identity = new ClaimsIdentity(CreateClaims(AuthorizedUser), AuthenticationType); user = new ClaimsPrincipal(identity); } return new AuthenticationState(user); } private static IEnumerable<Claim> CreateClaims(AuthorizedUser authorizedUser) { yield return new Claim(ClaimTypes.Name, authorizedUser.Name); var roles = authorizedUser.Roles?.Split(‘,’) ?? new string[0]; foreach (var role in roles) yield return new Claim(ClaimTypes.Role,role.Trim()); } } } Add the following code in the Program.cs file. using Microsoft.AspNetCore.Components.Authorization; using System.Net.Http; … public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.Services.AddAuthorizationCore(); builder.Services.AddScoped<ClientAuthorizationService>(CreateAuthorizationService); builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<ClientAuthorizationService>()); builder.Services.AddOptions(); … private static ClientAuthorizationService CreateAuthorizationService(IServiceProvider serviceProvider){ var httpClient = serviceProvider.GetRequiredService<HttpClient>(); var service = new ClientAuthorizationService(httpClient) { ApiUriGetAuthorizedUser = “api/settings/user”, ApiUriSignIn = “AzureADB2C/Account/SignIn”, ApiUriSignOut = “AzureADB2C/Account/SignOut”, }; return service; } Create a new Razor component SignInDisplay.razor in the shared folder. @using Sample.WebApp.Services@inject ClientAuthorizationService AuthorizationService<AuthorizeView> <Authorized> <div> <span class=”form-control”>@AuthorizationService.AuthorizedUser.Name</span> </div> <div> <a class=”btn btn-outline-primary” href=”@AuthorizationService.ApiUriSignOut”>Sign Out</a> </div> </Authorized> <NotAuthorized> <div> <a class=”btn btn-outline-primary” href=”@AuthorizationService.ApiUriSignIn”>Sign In</a> </div> </NotAuthorized> </AuthorizeView> In the Shared/MainLayout page, add the following code. <div class=”top-row px-4 auth”> <SignInDisplay /></div> Run the app with Sample.Api as the startup file and see the output as follows. Since Windows authorization is not yet implemented in the API, If you click the Sign In button, you will be rerouted to a page stating, “Sorry, there’s nothing at this address.” When you change the code in the GetUser() method in the SettingsController to include the name of the user, the app will be shown as follows. Refer to this link for more information.
How do you implement role-based authorization in Blazor?
Role-based authorization is a declarative way of limiting resource access that first appeared in ASP.NET (pre-Core). In order for the user to access certain resources, developers must specify a role that the user belongs to. This is done by using the [Authorize] attribute. Users can have a single role or multiple roles depending on the backing store used. The following procedure explains how to implement role-based authorization. Create a Blazor WebAssembly app and add the following role service in the Startup class under ConfigureServices. public void ConfigureServices(IServiceCollection services) { ………………….. . services.AddDefaultIdentity<IdentityUser>() .AddRoles<IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>();} Add specific roles in your database by overriding the OnModelCreating method of ApplicationDBContext. The User and Admin roles are added in the following code. public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<IdentityRole>().HasData(new IdentityRole { Name = “User”, NormalizedName = “USER”, Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() }); builder.Entity<IdentityRole>().HasData(new IdentityRole { Name = “Admin”, NormalizedName = “ADMIN”, Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() }); } } Once the roles are added, generate a migration and apply it to your database. Add – Migration SeedRolesUpdate-Database Add users to the roles by updating the action on the Accounts controller. All new users are added to the User role, except for the admin email.[AccountsController.cs] [HttpPost] public async Task<IActionResult> Post([FromBody]RegisterModel model) { var newUser = new IdentityUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync(newUser, model.Password); if (!result.Succeeded) { var errors = result.Errors.Select(x => x.Description); return BadRequest(new RegisterResult { Successful = false, Errors = errors }); } await _userManager.AddToRoleAsync(newUser, “User”); if (newUser.Email.StartsWith(“admin”)) { await _userManager.AddToRoleAsync(newUser, “Admin”); } return Ok(new RegisterResult { Successful = true }); } Update the Login method in the LoginController.Add roles as claims to the JSON web token (JWT) since we are assigning new users to roles at signup, so we need to pass this information to Blazor. Add the following code in the Login method. Current users can be taken through UserManager, which is used to get their roles. public async Task<IActionResult> Login([FromBody] LoginModel login){ …………….. . var user = await _signInManager.UserManager.FindByEmailAsync(login.Email); var roles = await _signInManager.UserManager.GetRolesAsync(user); var claims = new List<Claim>(); claims.Add(new Claim(ClaimTypes.Name, login.Email)); foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role)); } …………….. . . } Add roles in client-side Blazor.Once the new users are signed up, we have to get those roles via JWT. To do this, we add the following code in the ParseClaimsFromJwt method, which will take JWT, decode it, extract claims, and return it. private IEnumerable<Claim> ParseClaimsFromJwt(string jwt) { var claims = new List<Claim>(); var payload = jwt.Split(‘.’)[1]; var jsonBytes = ParseBase64WithoutPadding(payload); var keyValuePairs = JsonSerializer.Parse<Dictionary<string, object>>(jsonBytes); keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles); if (roles != null) { if (roles.ToString().Trim().StartsWith(“[“)) { var parsedRoles = JsonSerializer.Parse<string[]>(roles.ToString()); foreach (var parsedRole in parsedRoles) { claims.Add(new Claim(ClaimTypes.Role, parsedRole)); } } else { claims.Add(new Claim(ClaimTypes.Role, roles.ToString())); } keyValuePairs.Remove(ClaimTypes.Role); } claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()))); return claims; } private byte[] ParseBase64WithoutPadding(string base64) { switch (base64.Length % 4) { case 2: base64 += “==”; break; case 3: base64 += “=”; break; } return Convert.FromBase64String(base64); } We have to check that the first character is [, indicating it’s a JSON array. If the role claim is present and if the [ character is found, then we have to extract the individual role names from the roles entered. We have to loop these role names and add each as a claim, but if the role is not an array, then it is added as a single role claim. To call ParseClaimsFromJwt, we need to update the MarkUserAsAuthenticated method as shown. public void MarkUserAsAuthenticated(string token) { var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), “jwt”)); var authState = Task.FromResult(new AuthenticationState(authenticatedUser)); NotifyAuthenticationStateChanged(authState); } Update the Login method on the AuthService to pass the token rather than the email when calling MarkUserAsAuthenticated. public async Task<LoginResult> Login(LoginModel loginModel) { ……………. . var result = await _httpClient.PostJsonAsync<LoginResult>(“api/Login”, loginModel); if (result.Successful) { await _localStorage.SetItemAsync(“authToken”, result.Token); ((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(result.Token); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“bearer”, result.Token); return result; } return result; } Apply role-based authentication to the API.We can allow access to a specific page by an admin user alone through the Authorize attribute as shown. namespace BlazorWebAssembly.Server.Controllers { …………. . . public class SampleDataController : Controller { …… . . [Authorize(Roles = “Admin”)] [HttpGet(“[action]”)] public IEnumerable<WeatherForecast> WeatherForecasts() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }); } } Apply role-based authentication in the Blazor page.Use the @attribute directive with the [Authorize] attribute in a Blazor page to restrict the user access to specific pages. The following code allows the admin user alone to see the fetchdata page. @page “/fetchdata”@attribute [Authorize(Roles = “Admin”)]@using BlazorAuthorization.Shared When we log in using accounts other than admin, we are unable to load the fetchdata page. We can also add role-based authorization using the AuthorizeView component in the Index.razor page as shown in the following code. <AuthorizeView Roles=”User”> <p>You can only see this if you satisfy the IsUser policy.</p> </AuthorizeView> <AuthorizeView Roles=”Admin”> <p>You can only see this if you satisfy the IsAdmin policy.</p> </AuthorizeView> The output text is shown according to the accounts (admin/user) the user is signed in as. Refer to this link for further information about role-based
How do I implement Blazor authentication with Google?
Google authentication is a process by which the user can use their Google accounts to get authorized to access particular data. Follow this procedure to implement Google authentication. Prerequisites Visual Studio latest version .NET Core SDK latest version. Create a Blazor Server app with the latest .NET support. Set the Authentication Type as Individual Accounts and then click Create. Go to Tools > NuGet Package Manager > Package Manager Console. Before running the app, we have to perform migration by running the following command in the Package Manager Console. Update-Database To configure a Google API Console project, check whether SSL is enabled by right-clicking the project name, selecting Properties, and selecting the Debug property. I need this URL for configuration. To create a Google API Console project, follow these steps. Go to https://developers.google.com/identity/sign-in/web/sign-in#before_you_begin. Click the Credentials page link under the topic “Create authorization credentials.” Sign in with your Google account and create a project by providing a project name. Click Create Credentials and select OAuth client ID. Select the Application type and type the name of the OAuth ID and the redirect URI (localhost URL given to enable SSL in your app) as shown. Click Create and note your client ID and client secret, which are important for Google authentication. Install the Google Authentication middleware NuGet package by typing the following command in the Package Manager Console. NuGet\Install-Package Microsoft.AspNetCore.Authentication.Google -Version 7.0.9 Configure the Blazor app to use Google authentication. Right-click the project, select Manage User Secrets, and type the following code. { “Authentication:Google:ClientId”: “your Google client ID”, “Authentication:Google:ClientSecret”: “your Google client secret”} Open the Program.cs file and add the following code under the ConfigureServices method. builder.Services.AddAuthentication().AddGoogle(googleOptions =>{ googleOptions.ClientId = “Authentication:Google:Your Google ClientId”; googleOptions.ClientSecret =”Authentication:Google:Your Google ClientSecret here”;}); Note the following about this code: The AddGoogle() method is used to configure the authentication process in our application. ClientID and ClientSecret will be read from the secrets.json file by the code. Add Google authorization to the Blazor page.Use the Authorize attribute by using the @ directive in a Blazor page to restrict unauthorized users. @page “/fetchdata” @attribute [Authorize] @using Microsoft.AspNetCore.Authorization This code will allow the authorized user alone to see the fetchdata page Output The following image shows the output after running the application. The following image shows the output after clicking Login to navigate to the authorization page. The following image shows the output after the user is authorized using their Google account.