Monday 13 November 2023

Blazor with Identity Integration in ASP.NET Core

Project Setup step by step:-

--Create Blazor Web Assembly App

--Select Individual security option

--Build & Run

--Update Connection String:-

Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=BlazorAppDB;Integrated Security=True;Encrypt=False;TrustServerCertificate=False;

--

>dir

>cd .\BlazorIdentityAuthApp

>dir

>cd .\Server

>dotnet ef database update

--Now check DB got created in DB with default tables

--Try Register--Data will save in DB

-login and check

--Add additional migration

>dotnet ef migrations add UsersInfoMigration

>dotnet ef database update

--Add Identity:-

--Right click on .Server Project>>Add>>Scaffolding Item>>Select Identity>>Add


There are 3 different Projects will be created Client, Server and Shared.
On integrating Identity Authentication, there will be generate some inbuilt pages 

e.g: 

I. In .Client Project

Pages>>Authentication.razor

Shared>>LoginDisplay.razor & RedirectToLogin.razor


II. In .Server Project
Areas>>Identity>>Pages>>Account folder

Controllers>>OidConfigurationController.cs

Data folder>>Migration folder

Models folder>>ApplicationUser.cs



III. In .Shared Project


--We will start writing code snippet from .Server Project. 

First Add DBContext:

i. Data folder>>ApplicationDbContext.cs and below are the code snippet

using BlazorIdentityAuthApp.Server.Interfaces;

using BlazorIdentityAuthApp.Server.Models;

using BlazorIdentityAuthApp.Shared.Models;

using Duende.IdentityServer.EntityFramework.Options;

using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;

using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Options;


namespace BlazorIdentityAuthApp.Server.Data

{

    public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>

    {

        public ApplicationDbContext(

            DbContextOptions options,

            IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)

        {

        }


        public virtual DbSet<UsersInfo> UsersInfo { get; set; }


        protected override void OnModelCreating(ModelBuilder modelBuilder)

        {

            base.OnModelCreating(modelBuilder);

            // Configure the UsersInfo entity

            modelBuilder.Entity<UsersInfo>(entity =>

            {

                entity.HasKey(e => e.UserId);

                entity.Property(e => e.UserName).IsRequired();

                entity.Property(e => e.Address).IsRequired();

                entity.Property(e => e.MobileNo).IsRequired();

                entity.Property(e => e.EmailId).IsRequired();

            });

        }

    }

}

ii. Interfaces folder>>IUsersInfo.cs and below are the code snippet

using BlazorIdentityAuthApp.Shared.Models;

namespace BlazorIdentityAuthApp.Server.Interfaces

{

    public interface IUsersInfo

    {

        public List<UsersInfo> GetAllUsers();

        public UsersInfo GetUserInfo(int userId);

        public bool AddUser(UsersInfo usersInfo);

        public bool RemoveUser(int userId);

        public bool UpdateUser(UsersInfo usersInfo);

    }

}

iii. Services folder>>UsersInfoImpl.cs and below are the code snippet

using BlazorIdentityAuthApp.Server.Data;
using BlazorIdentityAuthApp.Server.Interfaces;
using BlazorIdentityAuthApp.Shared.Models;
using Microsoft.EntityFrameworkCore;

namespace BlazorIdentityAuthApp.Server.Services
{
    public class UsersInfoImpl:IUsersInfo
    {
        private readonly ApplicationDbContext context;
        public UsersInfoImpl(ApplicationDbContext context)
        {
            this.context = context;
        }

        public bool AddUser(UsersInfo usersInfo)
        {
            try
            {
                context.UsersInfo.Add(usersInfo);
                var res = context.SaveChanges();
                if (res > 0)
                    return true;
            }
            catch { throw; }
            return false;
        }

        public bool UpdateUser(UsersInfo usersInfo)
        {
            try
            {
                context.Entry(usersInfo).State = EntityState.Modified;
                var res = context.SaveChanges();
                if (res > 0)
                    return true;
            }
            catch { throw; }
            return false;
        }

        public List<UsersInfo> GetAllUsers()
        {
            try
            {
                return context.UsersInfo.ToList();
            }
            catch { throw; }
        }

        public UsersInfo GetUserInfo(int userId)
        {
            try
            {
                UsersInfo? userInfo = context.UsersInfo.Find(userId);
                if (userInfo != null)
                {
                    return userInfo;
                }
                else
                    throw new ArgumentNullException();
            }
            catch { throw; }
        }

        public bool RemoveUser(int userId)
        {
            try
            {
                UsersInfo? userInfo = context.UsersInfo.Find(userId);
                if (userInfo != null)
                {
                    context.UsersInfo.Remove(userInfo);
                    var res = context.SaveChanges();
                    if (res > 0)
                        return true;
                }
            }
            catch { throw; }
            return false;
        }

    }
}

iv. Controller folder>>UsersInfoController.cs and below are the code snippet

using BlazorIdentityAuthApp.Server.Interfaces;
using BlazorIdentityAuthApp.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace BlazorIdentityAuthApp.Server.Controllers
{
    [Route("api/User")]
    [ApiController]
    //[Authorize(Roles ="Admin,User")]
    public class UsersInfoController : ControllerBase
    {
        private readonly IUsersInfo user;
        public UsersInfoController(IUsersInfo user)
        {
            this.user = user;
        }

        [HttpGet]
        public async Task<List<UsersInfo>> Get()
        {
            return await Task.FromResult(user.GetAllUsers());
        }

        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            var res = user.GetUserInfo(id);
            if (res != null)
            {
                return Ok(res);
            }
            else
            {
                return NotFound();
            }
        }

        [HttpPost]
        public bool Post(UsersInfo userInfo)
        {
            var res = user.AddUser(userInfo);
            if (res)
                return true;
            return false;
        }

        [HttpPut]
        public bool Put(UsersInfo userInfo)
        {
            var res = user.UpdateUser(userInfo);
            if (res)
                return true;
            return false;
        }

        [HttpDelete("{id}")]
        [Authorize(Roles = "Admin")]
        public IActionResult Delete(int id)
        {
            var res = user.RemoveUser(id);
            if (res)
                return Ok(res);
            return NotFound();
        }

    }
}

v. Program.cs file and below are the code snippet

using BlazorIdentityAuthApp.Server.Data;

using BlazorIdentityAuthApp.Server.Models;

using Microsoft.AspNetCore.Authentication;

using Microsoft.AspNetCore.ResponseCompression;

using Microsoft.EntityFrameworkCore;

using Microsoft.AspNetCore.Identity;

using BlazorIdentityAuthApp.Server.Services;

using BlazorIdentityAuthApp.Server.Interfaces;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options =>

    options.UseSqlServer(connectionString));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

//Added Roles

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)

    .AddRoles<IdentityRole>()

    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()

    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

// Add services to the container.

builder.Services.AddScoped<IUsersInfo, UsersInfoImpl>();

//Added Auth Cookie

builder.Services.AddAuthentication("Identity.Application").AddCookie();

builder.Services.AddAuthentication()

    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();

builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.

if (app.Environment.IsDevelopment())

{

    app.UseMigrationsEndPoint();

    app.UseWebAssemblyDebugging();

}

else

{

    app.UseExceptionHandler("/Error");

    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.

    app.UseHsts();

}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();

app.UseStaticFiles();

app.UseRouting();

app.UseIdentityServer();

//Added Auth

app.UseAuthentication();

app.UseAuthorization();

app.MapRazorPages();

app.MapControllers();

app.MapFallbackToFile("index.html");

app.Run();


vi. appsettings.json file and below are the code snippet

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=BlazorAppDB;Integrated Security=True;Encrypt=False;TrustServerCertificate=False;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "IdentityServer": {
    "Clients": {
      "BlazorIdentityAuthApp.Client": {
        "Profile": "IdentityServerSPA"
      }
    }
  },
  "AllowedHosts": "*"
}


--Then we will start writing code snippet from .Shared Project. 

i. Models folder>>UsersInfo.cs

using System;

using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace BlazorIdentityAuthApp.Shared.Models

{

    public class UsersInfo

    {

        [Key] public int UserId { get; set; }

        [Required] public string UserName { get; set; }

        [Required] public string Address { get; set; }

        [Required] public long MobileNo { get; set; }

        [Required] public string EmailId { get; set; }

    }

}


--Then we will start writing code snippet from .Client Project.

i. Pages>>FetchUserDetails.razor and below are the code snippet

@page "/fetchuserdetails"
@using BlazorIdentityAuthApp.Shared.Models;
@using System.Net;
@using Microsoft.AspNetCore.Authorization;
@inject HttpClient Http;//it will get the HTTP Request/Response from the provided URI.
@*API's URI*@

<h1>User Data</h1>
<p>Blazor Application CRUD Operation</p>
<div class="row">
    <div class="col-md-6">
        <a href="/user/add" class="btn btn-primary" role="button">
            <i class="fas fa-user-plus"></i>
            Add User
        </a>
    </div>
    <div class="input-group col">
        <input type="text" class="form-control" placeholder="Search User By Name" @bind="SearchString" @bind:event="oninput" @onkeyup="FilterUser" />
        @if (SearchString.Length > 0)
        {
            <div class="input-group-append">
                <button class="btn btn-danger" @onclick="ResetSearch">Reset</button>
            </div>
        }
    </div>
</div>
<br />
@if (userList == null)
{
    <p><em>Loading......</em></p>
}
else
{
    <table class="table table-striped align-middle table-bordered">
        <thead class="table-success">
            <tr>
                <th>UserId</th>
                <th>UserName</th>
                <th>Address</th>
                <th>Mobile No</th>
                <th>EmailId</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var u in userList)
            {
                <tr>
                    <td>@u.UserId</td>
                    <td>@u.UserName</td>
                    <td>@u.Address</td>
                    <td>@u.MobileNo</td>
                    <td>@u.EmailId</td>
                    <td>
                        <a href="/user/edit/@u.UserId" class="btn btn-success" role="button">Edit</a>
                        <a href="/user/delete/@u.UserId" class="btn btn-danger" role="button">Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}
@code {
    protected List<UsersInfo> userList = new();
    protected List<UsersInfo> searchUserData = new();
    UsersInfo user = new();
    protected string SearchString { get; set; } = String.Empty;

    protected override async Task OnInitializedAsync()
    {
        await GetUser();
    }

    protected async Task GetUser()
    {
        userList = await Http.GetFromJsonAsync<List<UsersInfo>>("api/User");
        searchUserData = userList;
    }

    protected void FilterUser()
    {
        if (!string.IsNullOrEmpty(SearchString))
        {
            userList = searchUserData.Where(x => x.UserName.IndexOf(SearchString,
                StringComparison.OrdinalIgnoreCase) != -1).ToList();
        }
        else
        {
            userList = searchUserData;
        }
    }

    protected void DeleteConfirm(int UserID)
    {
        user = userList.FirstOrDefault(x => x.UserId == UserID);
    }

    public void ResetSearch()
    {
        SearchString = String.Empty;
        userList = searchUserData;
    }

}

ii. Pages>>AddUser.razor and below are the code snippet

@page "/user/add"
@page "/user/edit/{userId:int}"
@using BlazorIdentityAuthApp.Shared.Models;
@inject HttpClient Http;
@inject NavigationManager nav;

@*Navigation in Angular*@
<h1>@Title New User</h1>
<hr />
<h4 class="bg-success text-center text-white">@Result</h4>
<EditForm Model="@user" OnValidSubmit="SaveUser">
    <DataAnnotationsValidator />
    <div class="mb-3">
        <label>Enter Name</label>
        <InputText class="form-control" @bind-Value="user.UserName" />
        <span><ValidationMessage For="@(()=>user.UserName)" /></span>
    </div>
    <div class="mb-3">
        <label>Enter Address</label>
        <InputTextArea class="form-control" @bind-Value="user.Address" />
        <span><ValidationMessage For="@(()=>user.Address)" /></span>
    </div>
    <div class="mb-3">
        <label>Enter Mobile No</label>
        <InputNumber class="form-control" @bind-Value="user.MobileNo" />
        <span><ValidationMessage For="@(()=>user.MobileNo)" /></span>
    </div>
    <div class="mb-3">
        <label>Enter EmailId</label>
        <InputText class="form-control" @bind-Value="user.EmailId" />
        <span><ValidationMessage For="@(()=>user.EmailId)" /></span>
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-primary">@BtnTxt</button>
        <button type="reset" class="btn btn-light" @onclick="Cancel">Cancel</button>
    </div>
</EditForm>

@code {
    [Parameter]
    public int userId { get; set; }
    protected string Title = "Add";
    protected string BtnTxt = "Save";
    protected UsersInfo user = new();
    public string Result { get; set; } = string.Empty;
    protected override async Task OnParametersSetAsync()
    {
        if (userId != 0)
        {
            Title = "Edit";
            BtnTxt = "Update";
            user = await Http.GetFromJsonAsync<UsersInfo>("api/User/" + userId);
        }
    }

    protected async Task SaveUser()
    {
        if (user.UserId != 0)
        {
            await Http.PutAsJsonAsync("/api/User", user);
            Result = "User data updated successfully.";
        }
        else
        {
            await Http.PostAsJsonAsync("/api/User", user);
            Result = "New user added successfully.";
        }
    }

    protected void Cancel()
    {
        nav.NavigateTo("/fetchuserdetails");
    }
}

iii. Pages>>DeleteUser.razor and below are the code snippet

@page "/user/delete/{userId:int}"
@using BlazorIdentityAuthApp.Shared.Models;
@inject HttpClient Http;
@inject NavigationManager nav;
<h2>Delete User</h2>
<br />
<div class="form-group">
    <h4 class="bg-danger text-white">Do you want to delete user?</h4>
    <table class="table">
        <tbody>
            <tr>
                <td>Name</td>
                <td>@user.UserName</td>
            </tr>
            <tr>
                <td>Address</td>
                <td>@user.Address</td>
            </tr>
            <tr>
                <td>MobileNo</td>
                <td>@user.MobileNo</td>
            </tr>
            <tr>
                <td>EmailId</td>
                <td>@user.EmailId</td>
            </tr>
        </tbody>
    </table>
    <div class="form-group">
        <input type="submit" value="Delete" class="btn btn-danger" @onclick="(async()=>await RemoveUser(user.UserId))" />
        <input type="button" value="Cancel" class="btn btn-light" @onclick="(()=>Cancel())">Cancel</input>
    </div>

</div>

@code {
    [Parameter]
    public int userId { get; set; }
    UsersInfo user = new();

    protected override async Task OnInitializedAsync()
    {
        user = await Http.GetFromJsonAsync<UsersInfo>("api/User/" + userId);
    }

    protected void Cancel()
    {
        nav.NavigateTo("/fetchuserdetails");
    }

    protected async Task RemoveUser(int userId)
    {
        await Http.DeleteAsync("api/User/" + userId);
        nav.NavigateTo("/fetchuserdetails");
    }
}

iv. Shared>>NavMenu.razor and below are the code snippet

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">BlazorIdentityAuthApp</a>
        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <AuthorizeView>
            <Authorized>
                <div class="nav-item px-3">
                    <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                        <span class="oi oi-home" aria-hidden="true"></span> Home
                    </NavLink>
                </div>
                <div class="nav-item px-3">
                    <NavLink class="nav-link" href="counter">
                        <span class="oi oi-plus" aria-hidden="true"></span> Counter
                    </NavLink>
                </div>
                <div class="nav-item px-3">
                    <NavLink class="nav-link" href="fetchdata">
                        <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
                    </NavLink>
                </div>
                <div class="nav-item px-3">
                    <NavLink class="nav-link" href="fetchuserdetails">
                        <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch User Details
                    </NavLink>
                </div>
                <div class="nav-item px-3">
                    <NavLink class="nav-link" href="/identity/account/logout">
                        <span class="oi oi-power-standby" aria-hidden="true"></span> Log Out
                    </NavLink>
                </div>
            </Authorized>
            <NotAuthorized>
                <div class="nav-item px-3">
                    <NavLink class="nav-link" href="/identity/account/login">
                        <span class="oi oi-lock-locked" aria-hidden="true"></span> Login
                    </NavLink>
                </div>
            </NotAuthorized>
        </AuthorizeView>
    </nav>
</div>

@code {
    private bool collapseNavMenu = true;

    private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

v. App.razor and below are the code snippet

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (context.User.Identity?.IsAuthenticated != true)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p role="alert">You are not authorized to access this resource.</p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>Not found</PageTitle>
            <LayoutView Layout="@typeof(MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

vi. Program.cs and below are the code snippet

using BlazorIdentityAuthApp.Client;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddHttpClient("BlazorIdentityAuthApp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("BlazorIdentityAuthApp.ServerAPI"));

builder.Services.AddApiAuthorization();

await builder.Build().RunAsync();



No comments:

Post a Comment