Share via

im creating cutover tool using the blazor application cretae runbook and tasks codes and pages are added now i need help for profile, roles and permisiion

Gunashree M 0 Reputation points
2026-04-28T05:49:46.4933333+00:00

iam developing the cutover tool but now facing an issue with profile screen i mean logout and current logged in user, but not able to display on dashboard screen please help me with that

Developer technologies | .NET | Blazor
0 comments No comments

3 answers

Sort by: Most helpful
  1. Jack Dang (WICLOUD CORPORATION) 17,585 Reputation points Microsoft External Staff Moderator
    2026-04-28T08:20:43.7133333+00:00

    Hi @Gunashree M ,

    Thanks for sharing the code.

    There are a few separate issues working together, I'll go through them in order.

    Please note that the code snippets below are reference adjustments to your own files; namespaces, styling, and the exact claims you issue should be tuned to match your project. The Role property and any perm claims should come from your User entity / role-permission tables rather than being hardcoded.

    1. CustomAuthStateProvider is hardcoded and never reads the real user.

    Right now your provider always returns "admin" with the Admin role no matter who is signed in (or whether anyone is signed in at all). It also isn't reading the cookie that your /auth/signin endpoint issues. Replace it with one that pulls the principal from HttpContext via IHttpContextAccessor:

    using Microsoft.AspNetCore.Components.Authorization;
    using System.Security.Claims;
    
    public class CustomAuthStateProvider : AuthenticationStateProvider
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
    
        public CustomAuthStateProvider(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }
    
        public override Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var user = _httpContextAccessor.HttpContext?.User
                       ?? new ClaimsPrincipal(new ClaimsIdentity());
    
            return Task.FromResult(new AuthenticationState(user));
        }
    
        public void NotifyUserChanged() =>
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }
    
    1. The provider isn't registered in Program.cs.

    Without this registration, Blazor falls back to its default no-op provider and <AuthorizeView> always renders <NotAuthorized>. Add this in your services section:

    builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
    

    (AddHttpContextAccessor() is already in your Program.cs, so that part is fine.)

    1. Login.razor validates the password but never actually signs the user in.

    Your login page checks the password against Db.Users and then calls Nav.NavigateTo("/runbooks", forceLoad: true), but no cookie is ever issued, so on the next request HttpContext.User is still anonymous, and that's why your dashboard shows nothing for the user.

    You already have the right endpoint (MapPost("/auth/signin", ...)), the login page just needs to post to it instead of doing the check itself. The cleanest way in Blazor Server is to submit a regular HTML form so the browser hits the endpoint, the cookie gets set, and the browser is redirected back. Note this means stepping away from EditForm + LoginViewModel for this one page - EditForm.OnValidSubmit runs inside the SignalR circuit, where there's no HttpContext to write a cookie to. Client-side validation can stay as required/maxlength HTML attributes, and the server-side check moves into the endpoint.

    @page "/login"
    @inject NavigationManager Nav
    
    <h3>Login</h3>
    
    @if (!string.IsNullOrEmpty(_error))
    {
        <div class="alert alert-danger">@_error</div>
    }
    
    <form method="post" action="/auth/signin">
        <input type="hidden" name="ReturnUrl" value="/runbooks" />
    
        <div class="mb-3">
            <label>Username</label>
            <input class="form-control" name="Username" required />
        </div>
    
        <div class="mb-3">
            <label>Password</label>
            <input class="form-control" type="password" name="Password" required />
        </div>
    
        <div class="form-check mb-3">
            <input class="form-check-input" type="checkbox" name="RememberMe" value="true" />
            <label class="form-check-label">Remember me</label>
        </div>
    
        <button class="btn btn-primary" type="submit">Login</button>
        <button class="btn btn-secondary" type="button" @onclick="GoRegister">Register</button>
    </form>
    
    @code {
        private string? _error;
        private void GoRegister() => Nav.NavigateTo("/register");
    }
    

    Then update the /auth/signin endpoint to accept a form post and redirect on success, instead of returning JSON:

    app.MapPost("/auth/signin", async (
        HttpContext http,
        IUserService userService,
        [FromForm] string Username,
        [FromForm] string Password,
        [FromForm] string? ReturnUrl) =>
    {
        var user = await userService.ValidateLoginAsync(Username, Password);
        if (user is null)
            return Results.Redirect("/login?error=1");
    
        // Build the principal — these claims are what your AuthorizeView and policies will see
        var claims = new List<Claim>
        {
            new(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new(ClaimTypes.Name, user.Username),
            new(ClaimTypes.Role, user.Role ?? "User")
        };
    
        // Issue your AppPermissions as "perm" claims so the existing policies work
        var permissions = await userService.GetPermissionsAsync(user.Id);
        foreach (var p in permissions)
            claims.Add(new Claim("perm", p));
    
        var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
        var principal = new ClaimsPrincipal(identity);
    
        await http.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
    
        return Results.Redirect(string.IsNullOrEmpty(ReturnUrl) ? "/runbooks" : ReturnUrl);
    }).AllowAnonymous().DisableAntiforgery();
    

    The important part is HttpContext.SignInAsync(...), that's what writes the auth cookie. Without it, your CustomAuthStateProvider has nothing to read.

    1. Logout is currently a GET-style navigation to a POST endpoint.

    In ProfileMenu.razor you do Nav.NavigateTo("/auth/signout", forceLoad: true), but /auth/signout is mapped as MapPost, so the navigation 405s. Change the button to a form post (and you can drop the Logout() C# method and @onclick since the form handles it now):

    <form method="post" action="/auth/signout" style="display:inline;">
        <button type="submit"
                style="background:#FFD600; color:black; border:none;
                       padding:6px 12px; border-radius:4px; font-weight:600;">
            Logout
        </button>
    </form>
    

    And update the endpoint to call SignOutAsync and redirect:

    app.MapPost("/auth/signout", async (HttpContext http) =>
    {
        await http.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        return Results.Redirect("/login");
    }).AllowAnonymous().DisableAntiforgery();
    
    1. A couple of cleanups while you're in there.
    • The Microsoft.AspNetCore.Components.WebAssembly.Authentication package isn't used in Blazor Server, you can remove it to avoid confusion.
    • In App.razor, swap <RouteView ...> for <AuthorizeRouteView ...>. Without it, [Authorize] attributes on pages won't redirect unauthenticated users to /login, they'll just render the page anyway.
    • Add @attribute [Authorize] at the top of RunbookDashboard.razor (and any other page that requires login). That's what triggers the redirect for anonymous users.
    • Profile.razor doesn't need any changes, it'll start showing the real username and role as soon as step 1 is in place, because it reads the same AuthenticationStateProvider.

    Hope this helps! If my answer was helpful, I would greatly appreciate it if you could follow the instructions here so others with the same problem can benefit as well.


  2. Gunashree M 0 Reputation points
    2026-04-28T06:19:23.95+00:00

    if needed i will share all the codes which is already present in the blazor application please review and help me to acheave this installed this Microsoft.AspNetCore.Components.WebAssembly.Authentication.1.app.razor:<CascadingAuthenticationState>

    <Router AppAssembly="@typeof(App).Assembly">
    
        <Found Context="routeData">
    
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    
        </Found>
    
        <NotFound>
    
            <LayoutView Layout="@typeof(MainLayout)">
    
                <p>Sorry, there's nothing at this address.</p>
    
            </LayoutView>
    
        </NotFound>
    
    </Router>
    

    </CascadingAuthenticationState> 2.login.razor:@page "/login"

    @using UserRegistration.BlazorServer.ViewModels

    @using UserRegistration.BlazorServer.Data

    @using UserRegistration.BlazorServer.Domain.Entities

    @using Microsoft.EntityFrameworkCore

    @using Microsoft.AspNetCore.Components.Authorization

    @inject NavigationManager Nav

    @inject AppDbContext Db

    @inject Microsoft.AspNetCore.Identity.IPasswordHasher<User> Hasher

    <AuthorizeView Context="authState">

    <!-- ✅ USER NOT LOGGED IN -->
    
    <NotAuthorized>
    
        <h3>Login</h3>
    
        @if (!string.IsNullOrEmpty(_error))
    
        {
    
            <div class="alert alert-danger">@_error</div>
    
        }
    
        <EditForm Model="_model" OnValidSubmit="HandleLoginAsync">
    
            <DataAnnotationsValidator />
    
            <ValidationSummary />
    
            <div class="mb-3">
    
                <label>Username</label>
    
                <InputText class="form-control" @bind-Value="_model.Username" />
    
                <ValidationMessage For="@(() => _model.Username)" />
    
            </div>
    
            <div class="mb-3">
    
                <label>Password</label>
    
                <InputText class="form-control"
    
                           type="password"
    
                           @bind-Value="_model.Password" />
    
                <ValidationMessage For="@(() => _model.Password)" />
    
            </div>
    
            <div class="form-check mb-3">
    
                <InputCheckbox class="form-check-input"
    
                               @bind-Value="_model.RememberMe" />
    
                <label class="form-check-label">Remember me</label>
    
            </div>
    
            <div class="d-flex gap-2">
    
                <button class="btn btn-primary" type="submit">
    
                    Login
    
                </button>
    
                <button class="btn btn-secondary"
    
                        type="button"
    
                        @onclick="GoRegister">
    
                    Register
    
                </button>
    
            </div>
    
        </EditForm>
    
    </NotAuthorized>
    
    <!-- ✅ USER ALREADY LOGGED IN -->
    
    <Authorized>
    
        <p>You are already logged in.</p>
    
    </Authorized>
    

    </AuthorizeView>

    @code {

    private LoginViewModel _model = new();
    
    private string? _error;
    
    private async Task HandleLoginAsync()
    
    {
    
        _error = null;
    
        try
    
        {
    
            var username = _model.Username.Trim();
    
            var user = await Db.Users
    
                               .FirstOrDefaultAsync(u => u.Username == username);
    
            if (user is null)
    
            {
    
                _error = "Invalid username or password.";
    
                return;
    
            }
    
            var verify = Hasher.VerifyHashedPassword(
    
                user, user.PasswordHash, _model.Password);
    
            if (verify == Microsoft.AspNetCore.Identity.PasswordVerificationResult.Failed)
    
            {
    
                _error = "Invalid username or password.";
    
                return;
    
            }
    
            // ✅ SUCCESS → Redirect to dashboard
    
            Nav.NavigateTo("/runbooks", forceLoad: true);
    
        }
    
        catch
    
        {
    
            _error = "Something went wrong while signing in.";
    
        }
    
    }
    
    void GoRegister() => Nav.NavigateTo("/register");
    

    }

    `` 3.loginviewmodel.cs:using System.ComponentModel.DataAnnotations;

    namespace UserRegistration.BlazorServer.ViewModels

    {

    public class LoginViewModel
    
    {
    
        [Required(ErrorMessage = "Username is required")]
    
        [MaxLength(100, ErrorMessage = "Username is too long")]
    
        public string Username { get; set; } = string.Empty;
    
        [Required(ErrorMessage = "Password is required")]
    
        [MinLength(6, ErrorMessage = "Password must be at least 6 characters")]
    
        public string Password { get; set; } = string.Empty;
    
        public bool RememberMe { get; set; } = false;
    
    }
    

    } 4.mainlayout.razor:@inherits LayoutComponentBase

    <div class="page">

    <div class="sidebar">
    
        <NavMenu />
    
    </div>
    
    <main>
    
        <div class="top-row px-4"
    
             style="display:flex; align-items:center; justify-content:space-between;">
    
            <a href="https://learn.microsoft.com/aspnet/core/blazor" target="_blank">
    
                Docs
    
            </a>
    
            <!-- ✅ PROFILE MENU -->
    
            <ProfileMenu />
    
        </div>
    
        <div class="content px-4">
    
            @Body
    
        </div>
    
    </main>
    

    </div> 5.runbookdashboard.razor:@page "/runbooks"

    @layout DashboardLayout

    @using Microsoft.EntityFrameworkCore

    @using Microsoft.AspNetCore.Components.Routing

    @using UserRegistration.BlazorServer.Data

    @using UserRegistration.BlazorServer.Domain.Entities

    @using UserRegistration.BlazorServer.Domain.DTOs

    @inject AppDbContext Db

    @inject NavigationManager Nav

    <!-- ACTION BUTTONS (Use regular buttons for PERFECT navigation) -->

    <div style="display:flex; gap:20px; margin-bottom:30px;">

    <button type="button"
    
            style="background:#FFD600; padding:10px 20px; border:none;
    
                   font-weight:600; color:black; border-radius:5px;"
    
            @onclick="GoCreate">
    
        + Create Runbook
    
    </button>
    
    <button type="button"
    
            style="background:#FFD600; padding:10px 20px; border:none;
    
                   font-weight:600; color:black; border-radius:5px;"
    
            @onclick="GoWorkspace">
    
        My Workspace
    
    </button>
    

    </div>

    <!-- MAIN GRID -->

    <div style="display:flex; gap:35px;">

    <!-- LEFT: LIVE RUNBOOKS -->
    
    <div style="flex:2; background:#111; padding:20px; border-radius:10px;">
    
        <h3 style="color:white; margin:0 0 12px 0;">Live Runbooks</h3>
    
        @if (_runbooks.Count == 0)
    
        {
    
            <div style="color:#bbb; margin-top:10px;">No runbooks scheduled for today.</div>
    
        }
    
        else
    
        {
    
            @foreach (var r in _runbooks)
    
            {
    
                <div style="background:#222; padding:15px; border-radius:10px; margin-top:12px;">
    
                    <div style="font-size:18px; font-weight:600;">@r.Name</div>
    
                    <div style="color:#bbb; margin:6px 0 10px;">
    
                        @(r.StartTimeUtc?.ToLocalTime().ToString("dd MMM yyyy HH:mm") ?? "-")
    
                        –
    
                        @(r.EndTimeUtc?.ToLocalTime().ToString("dd MMM yyyy HH:mm") ?? "-")
    
                    </div>
    
                    @{
    
                        // Map Active/Completed/Cancelled visually
    
                        var label = r.Status == "Active" ? "Active" : r.Status;
    
                        var pill = r.Status switch
    
                        {
    
                                                                            "Active" => "background:#28a745; color:white;",
    
                                                                            "Completed" => "background:#6c757d; color:white;",
    
                                                                            "Cancelled" => "background:#dc3545; color:white;",
    
                            _ => "background:#FFD600; color:black;"
    
                        };
    
                    }
    
                    <span style="@pill padding:4px 10px; border-radius:5px; margin-right:10px;">
    
                        @label
    
                    </span>
    
                    <!-- COMPLETE BUTTON -->
    
                    <button type="button"
    
                            class="btn btn-sm btn-outline-light"
    
                            disabled="@(r.Status == "Completed" || r.Status == "Cancelled")"
    
                            @onclick="@(() => MarkCompleteAsync(r.Id))">
    
                        Complete
    
                    </button>
    
                    <!-- CANCEL BUTTON -->
    
                    @if (r.Status != "Completed" && r.Status != "Cancelled")
    
                    {
    
                        <button type="button"
    
                                class="btn btn-sm btn-outline-light"
    
                                style="margin-left:10px;"
    
                                @onclick="@(() => MarkCancelAsync(r.Id))">
    
                            Cancel
    
                        </button>
    
                    }
    
                    <!-- DELETE BUTTON -->
    
                    <button type="button"
    
                            class="btn btn-sm btn-danger"
    
                            style="margin-left:10px;"
    
                            @onclick="@(() => DeleteAsync(r.Id))">
    
                        Delete
    
                    </button>
    
                </div>
    
            }
    
        }
    
    </div>
    
    <!-- RIGHT: RUNBOOK ACTIVITY -->
    
    <div style="flex:1; background:#111; padding:20px; border-radius:10px;">
    
        <h3 style="color:white;">Runbook Activity</h3>
    
        <div style="background:#333; padding:12px; border-radius:8px; margin-top:12px;">
    
            @_summary.InProgress IN PROGRESS
    
        </div>
    
        <div style="background:#333; padding:12px; border-radius:8px; margin-top:12px;">
    
            @_summary.Completed COMPLETED
    
        </div>
    
        <div style="background:#333; padding:12px; border-radius:8px; margin-top:12px;">
    
            @_summary.Overdue OVERDUE
    
        </div>
    
        <div style="background:#333; padding:12px; border-radius:8px; margin-top:12px;">
    
            @_summary.Cancelled CANCELLED
    
        </div>
    
    </div>
    

    </div>

    @code {

    private List<Runbook> _runbooks = new();
    
    private RunbookActivitySummary _summary = new();
    
    protected override async Task OnInitializedAsync()
    
    {
    
        await LoadAsync();
    
    }
    
    private async Task LoadAsync()
    
    {
    
        var now = DateTime.UtcNow;
    
        _runbooks = await Db.Runbooks
    
                            .AsNoTracking()
    
                            .OrderByDescending(r => r.Id)
    
                            .ToListAsync();
    
        _summary = new RunbookActivitySummary
    
        {
    
            InProgress = _runbooks.Count(r => r.Status == "Active"),
    
            Completed = _runbooks.Count(r => r.Status == "Completed"),
    
            Cancelled = _runbooks.Count(r => r.Status == "Cancelled"),
    
            Overdue = _runbooks.Count(r =>
    
                r.EndTimeUtc < now &&
    
                r.Status != "Completed" &&
    
                r.Status != "Cancelled")
    
        };
    
    }
    
    private void GoCreate() => Nav.NavigateTo("/create_runbook", true);
    
    private void GoWorkspace() => Nav.NavigateTo("/workspace", true);
    
    private async Task MarkCompleteAsync(int id)
    
    {
    
        var rb = await Db.Runbooks.FindAsync(id);
    
        if (rb is null || rb.Status == "Completed" || rb.Status == "Cancelled")
    
            return;
    
        rb.Status = "Completed";
    
        await Db.SaveChangesAsync();
    
        await LoadAsync();
    
    }
    
    private async Task MarkCancelAsync(int id)
    
    {
    
        var rb = await Db.Runbooks.FindAsync(id);
    
        if (rb is null || rb.Status == "Completed" || rb.Status == "Cancelled")
    
            return;
    
        rb.Status = "Cancelled";
    
        await Db.SaveChangesAsync();
    
        await LoadAsync();
    
    }
    
    private async Task DeleteAsync(int id)
    
    {
    
        var rb = await Db.Runbooks.FindAsync(id);
    
        if (rb is null) return;
    
        Db.Runbooks.Remove(rb);
    
        await Db.SaveChangesAsync();
    
        await LoadAsync();
    
    }
    

    }

    6.dashboardlayout.razor:@inherits LayoutComponentBase

    <div style="background:#000; min-height:100vh; color:white;">

    <!-- EY HEADER -->
    
    <div style="display:flex; justify-content:space-between; align-items:center;
    
                padding:15px 25px; background:#111;">
    
        <div style="display:flex; align-items:center; gap:15px;">
    
            <img src="ey-logo.png" style="height:30px;" />
    
            <span style="font-size:20px; font-weight:700;">
    
                Cutover Runbook Dashboard
    
            </span>
    
        </div>
    
        <!-- ✅ AUTH‑AWARE PROFILE MENU -->
    
        <ProfileMenu />
    
    </div>
    
    <!-- BODY -->
    
    <div style="padding:20px;">
    
        @Body
    
    </div>
    

    </div>

    7.customauthstatprovider.cs:using Microsoft.AspNetCore.Components.Authorization;

    using System.Security.Claims;

    public class CustomAuthStateProvider : AuthenticationStateProvider

    {

    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    
    {
    
        // TEMPORARY: CHANGE THIS AFTER LOGIN
    
        // This example assumes user is logged in
    
        var claims = new List<Claim>
    
        {
    
            new Claim(ClaimTypes.Name, "admin"),
    
            new Claim(ClaimTypes.Role, "Admin")
    
        };
    
        // 🔴 THIS IS THE MOST IMPORTANT LINE
    
        var identity = new ClaimsIdentity(claims, "Cookies");
    
        var user = new ClaimsPrincipal(identity);
    
        return Task.FromResult(new AuthenticationState(user));
    
    }
    

    } 8.profiemenu.razor:@using System.Security.Claims

    @using Microsoft.AspNetCore.Components.Authorization

    @inject NavigationManager Nav

    <AuthorizeView>

    <Authorized Context="auth">
    
        @{
    
            var user = auth.User;
    
            var username =
    
            user.FindFirst(ClaimTypes.Name)?.Value
    
            ?? user.Identity?.Name
    
            ?? "User";
    
        }
    
        <div style="display:flex; align-items:center; gap:12px;">
    
            <!-- USERNAME -->
    
            <span style="color:white; font-weight:600;">
    
                @username
    
            </span>
    
            <!-- LOGOUT BUTTON -->
    
            <button style="background:#FFD600; color:black;
    
                           border:none; padding:6px 12px;
    
                           border-radius:4px; font-weight:600;"
    
                    @onclick="Logout">
    
                Logout
    
            </button>
    
        </div>
    
    </Authorized>
    
    <NotAuthorized>
    
        <button style="background:#8a2be2; color:white;
    
                       border:none; padding:6px 12px;
    
                       border-radius:4px;"
    
                @onclick="GoLogin">
    
            Login
    
        </button>
    
    </NotAuthorized>
    

    </AuthorizeView>

    @code {

    private void Logout()
    
    {
    
        // Force full reload so cookie is cleared
    
        Nav.NavigateTo("/auth/signout", forceLoad: true);
    
    }
    
    private void GoLogin()
    
    {
    
        Nav.NavigateTo("/login");
    
    }
    

    }

    `` 9.profile.razor:@page "/profile"

    @using System.Security.Claims

    @using Microsoft.AspNetCore.Components.Authorization

    @inject AuthenticationStateProvider AuthStateProvider

    <h2 style="color:white;">My Profile</h2>

    <div style="background:#111; padding:20px; border-radius:10px;

            max-width:400px; margin-top:20px; color:white;">
    
    <div style="margin-bottom:12px;">
    
        <strong>Username</strong><br />
    
        @username
    
    </div>
    
    <div>
    
        <strong>Role</strong><br />
    
        @role
    
    </div>
    

    </div>

    @code {

    private string username = "";
    
    private string role = "";
    
    protected override async Task OnInitializedAsync()
    
    {
    
        var authState = await AuthStateProvider.GetAuthenticationStateAsync();
    
        var user = authState.User;
    
        username =
    
            user.FindFirst(ClaimTypes.Name)?.Value
    
            ?? user.FindFirst(ClaimTypes.Email)?.Value
    
            ?? "User";
    
        role =
    
            user.FindFirst(ClaimTypes.Role)?.Value
    
            ?? "-";
    
    }
    

    } 10.program.cs:using Microsoft.AspNetCore.Authentication.Cookies;

    using Microsoft.AspNetCore.Identity;

    using Microsoft.EntityFrameworkCore;

    using Microsoft.AspNetCore.Http;

    using UserRegistration.BlazorServer.Authorization;

    using UserRegistration.BlazorServer.Data;

    using UserRegistration.BlazorServer.Domain.Entities;

    using UserRegistration.BlazorServer.Services;

    using UserRegistration.BlazorServer.ViewModels;

    var builder = WebApplication.CreateBuilder(args);

    // ------------------------------------

    // DB CONFIG

    // ------------------------------------

    var defaultDataDir = Path.Combine(builder.Environment.ContentRootPath, "App_Data");

    Directory.CreateDirectory(defaultDataDir);

    var defaultDbPath = Path.Combine(defaultDataDir, "app.db");

    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")

    ?? $"Data Source={defaultDbPath}";
    

    // ------------------------------------

    // SERVICES

    // ------------------------------------

    builder.Services.AddRazorPages();

    builder.Services.AddServerSideBlazor();

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

    options.UseSqlite(connectionString));
    

    builder.Services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();

    builder.Services.AddScoped<IUserService, UserService>();

    builder.Services.AddScoped<RegisterViewModel>();

    builder.Services.AddHttpContextAccessor();

    // ✅ SAFE HttpClient for Blazor Server

    builder.Services.AddScoped(sp =>

    {

    var ctx = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
    
    var baseUri = $"{ctx?.Request.Scheme}://{ctx?.Request.Host}";
    
    return new HttpClient { BaseAddress = new Uri(baseUri) };
    

    });

    // ------------------------------------

    // AUTHENTICATION

    // ------------------------------------

    builder.Services

    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    
    .AddCookie(options =>
    
    {
    
        options.LoginPath = "/login";
    
        options.AccessDeniedPath = "/access-denied";
    
        options.ExpireTimeSpan = TimeSpan.FromHours(8);
    
        options.SlidingExpiration = true;
    
        options.Cookie.HttpOnly = true;
    
        options.Cookie.SameSite = SameSiteMode.Lax;
    
        options.Cookie.SecurePolicy = builder.Environment.IsDevelopment()
    
            ? CookieSecurePolicy.SameAsRequest
    
            : CookieSecurePolicy.Always;
    
    });
    

    // ------------------------------------

    // AUTHORIZATION (PERMISSIONS)

    // ------------------------------------

    builder.Services.AddAuthorizationBuilder()

    .AddPolicy("CanCreateRunbook",
    
        p => p.RequireClaim("perm", AppPermissions.CreateRunbook))
    
    .AddPolicy("CanCreateTask",
    
        p => p.RequireClaim("perm", AppPermissions.CreateTask))
    
    .AddPolicy("CanTaskEdit",
    
        p => p.RequireClaim("perm", AppPermissions.TaskEdit))
    
    .AddPolicy("CanTaskDelay",
    
        p => p.RequireClaim("perm", AppPermissions.TaskDelay))
    
    .AddPolicy("CanTaskComplete",
    
        p => p.RequireClaim("perm", AppPermissions.TaskComplete));
    

    var app = builder.Build();

    // ------------------------------------

    // DB MIGRATION

    // ------------------------------------

    using (var scope = app.Services.CreateScope())

    {

    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    
    db.Database.Migrate();
    

    }

    // ------------------------------------

    // PIPELINE

    // ------------------------------------

    if (!app.Environment.IsDevelopment())

    {

    app.UseExceptionHandler("/Error");
    
    app.UseHsts();
    

    }

    app.UseHttpsRedirection();

    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();

    app.UseAuthorization();

    // ------------------------------------

    // ✅ LOGIN ENDPOINT (COOKIE ISSUED HERE)

    // ------------------------------------

    app.MapPost("/auth/signin", async (

    IUserService userService,
    
    LoginViewModel model) =>
    

    {

    var user = await userService.ValidateLoginAsync(
    
        model.Username,
    
        model.Password);
    
    if (user == null)
    
        return Results.BadRequest("Invalid credentials");
    
    await userService.SignInAsync(user);
    
    return Results.Ok();
    

    }).AllowAnonymous();

    // ------------------------------------

    // LOGOUT

    // ------------------------------------

    app.MapPost("/auth/signout", async (IUserService userService) =>

    {

    await userService.SignOutAsync();
    
    return Results.Redirect("/login");
    

    }).AllowAnonymous();

    app.MapBlazorHub();

    app.MapFallbackToPage("/_Host");

    app.Run();

    0 comments No comments

  3. Q&A Assist
    2026-04-28T05:50:00.9766667+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    To show the current logged-in user and a logout option on a Blazor dashboard, use the built-in authentication components and services from Microsoft.AspNetCore.Components.WebAssembly.Authentication.

    1. Make sure the authentication package/namespace is available
    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    1. Create a LoginDisplay-style component

    Create Shared/LoginDisplay.razor and use the authentication abstractions to show the current user and login/logout links or buttons.

    Example pattern:

    @using Microsoft.AspNetCore.Components.Authorization
    @using Microsoft.AspNetCore.Components.WebAssembly.Authentication
    @inject NavigationManager Navigation
    @inject SignOutSessionStateManager SignOutManager
    @inject AuthenticationStateProvider AuthenticationStateProvider
    
    <AuthorizeView>
        <Authorized>
            @if (userName is not null)
            {
                <span>Hello, @userName!</span>
            }
    
            <button @onclick="BeginLogout">Log out</button>
        </Authorized>
        <NotAuthorized>
            <a href="authentication/login">Log in</a>
        </NotAuthorized>
    </AuthorizeView>
    
    @code {
        private string? userName;
    
        protected override async Task OnInitializedAsync()
        {
            var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
            var user = authState.User;
    
            if (user.Identity?.IsAuthenticated == true)
            {
                // Use preferred claim (name, email, etc.)
                userName = user.Identity.Name ?? user.FindFirst("name")?.Value;
            }
        }
    
        private async Task BeginLogout()
        {
            // For WebAssembly with OIDC/MSAL, redirect to the built-in logout route
            await SignOutManager.SetSignOutState();
            Navigation.NavigateTo("authentication/logout");
        }
    }
    
    1. Add the component to the dashboard layout/page

    If using a layout like MainLayout.razor, place the login display where it should appear (header, nav bar, etc.):

    @inherits LayoutComponentBase
    
    <div class="page">
        <header>
            <LoginDisplay />
        </header>
    
        <main>
            @Body
        </main>
    </div>
    

    Or directly on the dashboard page:

    @page "/dashboard"
    
    <h3>Dashboard</h3>
    <LoginDisplay />
    
    <!-- rest of dashboard -->
    
    1. Ensure the authentication routes exist

    The Authentication.razor page defines the routes for login/logout and other auth actions. It should look like this:

    @page "/authentication/{action}"
    @using Microsoft.AspNetCore.Components.WebAssembly.Authentication
    
    <RemoteAuthenticatorView Action="@Action" />
    
    @code {
        [Parameter]
        public string? Action { get; set; }
    }
    
    1. Confirm authentication is configured in Program.cs

    For WebAssembly with MSAL/Entra ID or other OIDC provider, AddMsalAuthentication (or equivalent) must be configured and the app must use the RemoteAuthenticatorView routes above. This ensures authentication/login and authentication/logout work.

    Once these pieces are in place, the dashboard will show the current logged-in user and a working logout button.


    References:

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.