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.
How do you use Blazor in an existing ASP.NET MVC application?
Blazor applications are component-based. Blazor components can be used in existing ASP.NET MVC applications. Follow these steps to learn how Blazor components are used in the view page of an MVC application. Prerequisites: Visual Studio 2019 .NET Core 3.1 Create a ASP.NET MVC application. Open Visual Studio 2019 and select ASP.NET Core Web App (Model-View-Controller) in the Create a new project page and configure the project as shown. Add the reference Microsoft.AspNetCore.Components in your dependencies. Add the Blazor component folder in the View/Shared folder. Then add a Razor component inside the Component folder. Add the following code to the created Component.razor file. @using Microsoft.AspNetCore.Components <h3>Blazor Component in MVC</h3> <button @onclick=”Test” class=”btn btn-dark”>Click to get answer</button> <br /> <div >@Data </div> @code { [Parameter] public string Data { get; set; } = string.Empty; private void Test() { Data = “Button Clicked”; } } Add the script reference to the _Layout.cshtml file. <base href=”~/” /><script src=”_framework/blazor.server.js”></script> Create an _Imports.razor file in the Component folder and add the following namespaces to the _Imports.razor file to access component features over your components in the application. @using System.Net.Http@using Microsoft.AspNetCore.Authorization@using Microsoft.AspNetCore.Components.Authorization@using Microsoft.AspNetCore.Components.Forms@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.Web@using Microsoft.JSInterop@using System.IO Add services.AddServerSideBlazor() under the ConfigureServices method and add endpoints.MapBlazorHub(); under the Configure method in the Startup.cs file. ………………… . .namespace blazinmvc{ public class Startup { ……………… . . public void ConfigureServices(IServiceCollection services) { services.AddServerSideBlazor(); services.AddControllersWithViews(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { …………….. . . app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: “default”, pattern: “{controller=Home}/{action=Index}/{id?}”); endpoints.MapBlazorHub(); }); } } } To render components on the View page, add the following code in the Index.cshtml page in the Views folder. @{ ViewData[“Title”] = “Home Page”; }<div >@(await Html.RenderComponentAsync<blazinmvc.Views.Shared.Component.Component>(RenderMode.ServerPrerendered,new { Data= ” Hello World ” })) </div> Run the application. After button click In the component page, we use the button to change the text defined in the Index.cshtml page inside the component rendering. Clicking the button will change the text shown on the Home page. View Sample in GitHub
How do I generate and save a file client-side using Blazor?
Since there is no built-in functionality for saving files in Blazor, it is necessary to write the function in JavaScript and then invoke the JavaScript function from C# code in Blazor. In this solution, we have used JavaScript interop for file saving and generation in client-side Blazor. Create a new JavaScript file, saveFile.js, in the wwwroot folder and add the following code. [saveFile.js] function saveFile(file, Content) { var link = document.createElement(‘a’); link.download = name; link.href = “data:text/plain;charset=utf-8,” + encodeURIComponent(Content) document.body.appendChild(link); link.click(); document.body.removeChild(link); } Reference the new script file in the index.html page as shown.[index.html] <body> ………………………… . . <script src=”_framework/blazor.webassembly.js”></script> <script src=”saveFile.js”></script> </body> Invoke the JavaScript function in a new Razor page. [Savefile.razor] page “/savefile” @inject IJSRuntime JSRuntime <h1>Blazor Save & Generate File</h1><textarea @bind=”fileContent” style=”width:150px;height:100px” /> <button @onclick=”SaveFile”>SaveFile</button> <button @onclick=”DownloadFile”>GenerateFile</button> @code { string Content; string fileContent; string fileName = “file.txt”; public void SaveFile() { Content = fileContent; } public async void DownloadFile() { await JSRuntime.InvokeAsync<object>(“saveFile”,fileName,Content); }} Run the app. Save and generate the file in text format. View Sample in GitHub