How to maintain Authentication State with Blazor 8 Server Interactive

Posted 44 days ago by GregFinzer
0

I have a setup a full example project here with a kludge where I have to manually call it to grab the state on every page. The problem is if the user refreshes the page then it will redirect to login because the Authorize will hit before the OnAfterRenderAsync

https://github.com/GregFinzer/Blazor8Auth

I want to use what is built into Blazor 8 but I am not able to figure out the correct configuration. Here is what I have in my program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

//Authentication
builder.Services.AddAuthorization();

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Cookie.Name = "auth_token";
        options.LoginPath = "/login";
        options.Cookie.MaxAge = TimeSpan.FromHours(24);
        options.AccessDeniedPath = "/acessDenied";
    });

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

builder.Services.AddBlazoredSessionStorage();
builder.Services.AddScoped<ICustomSessionService, CustomSessionService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // 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.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

Here is my AuthService that I am calling on every page

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;

namespace Blazor8Auth.Services
{
    public class AuthService
    {
        const string AuthTokenName = "auth_token";
        public event Action<ClaimsPrincipal>? UserChanged;
        private ClaimsPrincipal? currentUser;
        private readonly ICustomSessionService _sessionService;
        private readonly IConfiguration _configuration;

        public AuthService(ICustomSessionService sessionService, IConfiguration configuration)
        {
            _sessionService = sessionService;
            _configuration = configuration;
        }

        public ClaimsPrincipal CurrentUser
        {
            get { return currentUser ?? new(); }
            set
            {
                currentUser = value;

                if (UserChanged is not null)
                {
                    UserChanged(currentUser);
                }
            }
        }

        public bool IsLoggedIn => CurrentUser.Identity?.IsAuthenticated ?? false;

        public async Task LogoutAsync()
        {
            CurrentUser = new();
            string authToken = await _sessionService.GetItemAsStringAsync(AuthTokenName);

            if (!string.IsNullOrEmpty(authToken))
            {
                await _sessionService.RemoveItemAsync(AuthTokenName);
            }
        }

        public async Task GetStateFromTokenAsync()
        {
            string authToken = await _sessionService.GetItemAsStringAsync(AuthTokenName);

            var identity = new ClaimsIdentity();

            if (!string.IsNullOrEmpty(authToken))
            {
                try
                {
                    var tokenHandler = new JwtSecurityTokenHandler();
                    var key = System.Text.Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value);

                    tokenHandler.ValidateToken(authToken, new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        ClockSkew = TimeSpan.Zero
                    }, out SecurityToken validatedToken);

                    var jwtToken = (JwtSecurityToken)validatedToken;
                    identity = new ClaimsIdentity(jwtToken.Claims, "jwt");
                }
                catch
                {
                    await _sessionService.RemoveItemAsync(AuthTokenName);
                    identity = new ClaimsIdentity();
                }
            }

            var user = new ClaimsPrincipal(identity);
            CurrentUser = user;
        }


        public async Task Login(ClaimsPrincipal user)
        {
            CurrentUser = user;

            var tokenEncryptionKey = _configuration.GetSection("AppSettings:Token").Value;
            var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8
                .GetBytes(tokenEncryptionKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
            var tokenHoursString = _configuration.GetSection("AppSettings:TokenHours").Value;
            int.TryParse(tokenHoursString, out int tokenHours);
            var token = new JwtSecurityToken(
                claims: user.Claims,
                expires: DateTime.Now.AddHours(tokenHours),
                signingCredentials: creds);

            var jwt = new JwtSecurityTokenHandler().WriteToken(token);
            await _sessionService.SetItemAsStringAsync(AuthTokenName, jwt);
        }
    }
}

Here is the code that has to be on every page:

@code {
    [Inject] private AuthService AuthService { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!AuthService.IsLoggedIn)
        {
            await AuthService.GetStateFromTokenAsync();
        }
    }
}

 

 

  • 0

    I finally figured out what I would call an interim solution where the state is maintained both on the Browser Session and the Blazor Back End.  I don't have to put auth code on every page now.  See the giant readme.md  https://github.com/GregFinzer/Blazor8Auth

    Posted 41 days ago by GregFinzer Edited 41 days ago
Someone is typing...

Post a Reply

You must be logged in to add a new post.
Number of online users: 0
An error has occurred. This application may no longer respond until reloaded. Reload 🗙