Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4183

Add claims to user from database post login

$
0
0

I have a Blazor ServerSide application, where I am adding authentication.

I have 2 ways to authenticate, depending on development or production.In production,we use OpenID.

In dev, the user is always authenticated, with the same claims that an OpenID gives.

My end goal is to authenticate via OpenID, and post authentication, check if the user is in the database.
If they are not, I want to add them.
Lastly, I want to add the userId(database primary key for the user) to the users claims.

My current setup, is I use an AuthenticationHandler to login, and afterwards an AuthenticationStateProvider to append some claims from the database.I had it breifly working yesterday with the OpenID connectrion.Afterwards, I added the DummyAuthenticationHandler, and moved things around.
Now, it doesn't work for any of the two ways.Unfortunally, I did not commit, and cannot go back to when it worked.

In my compenetns, I am retriving the AuthenticationState via a CascadingParameter

[CascadingParameter]public Task<AuthenticationState>? AuthState { get; set; }

The issue is, that when the login is triggered from a AuthenticationHandler, it overwrites the _initialValueFactory in CascadingValueSource with the new AuthencitaionState, and the code I have in my CustomAuthenticationStateProvider is therefore never invoked.

So, my qustion is, how can I achive my end goal, and have it working for both my dummy authentication and my real authentication?
I have read a lot of MS docs, but not found a soluition.
I am unsure if the apporch I am on, is even the correct one.

I use the following code to add my authentication

Program.cs

internal async Task CreateApplication(){    var builder = WebApplication.CreateBuilder(args);    // ... Some code to set fields    // ... Adding services    // Add Blazor    _services        .AddRazorComponents()        .AddInteractiveWebAssemblyComponents()        .AddInteractiveServerComponents();    _services.AddRadzenComponents();    _services.AddServerSideBlazor();    AddAuthentication();    var app = builder.Build();    app        .UseStaticFiles()        .UseRouting()        .UseAntiforgery();    app.MapRazorComponents<App>()        .AddInteractiveWebAssemblyRenderMode()        .AddInteractiveServerRenderMode();    app        .UseMiddleware<ExceptionMiddleware>()        .UseAuthentication()        .UseAuthorization();    await app.RunAsync();}
private void AddAuthentication(){    // Check if authentication is disabled    if (_environment.IsDevelopment())    {        _services.AddAuthentication(nameof(DummyAuthenticationHandler))            .AddScheme<DummyAuthenticationOptions, DummyAuthenticationHandler>(nameof(DummyAuthenticationHandler), _ => {});    }    else    {        _services.AddAuthentication(options =>            {                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;            })            .AddCookie()            .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>            {                options.Authority = _options.Authentication.Authority;                options.ClientId = _options.Authentication.ClientId;                options.ClientSecret = _options.Authentication.ClientSecret;                options.ResponseType = OpenIdConnectResponseType.Code;                options.Scope.Clear();                options.Scope.Add("openid");                options.Scope.Add("profile");                options.Scope.Add("email");                options.GetClaimsFromUserInfoEndpoint = true;                options.SaveTokens = true;                options.MapInboundClaims = false;                options.TokenValidationParameters.NameClaimType = OAuthClaim.Name;            });    }    _services.AddAuthorization();    _services.AddCascadingAuthenticationState();    _services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();}

DummyAuthenticationHandler

public class DummyAuthenticationOptions : AuthenticationSchemeOptions;public class DummyAuthenticationHandler(    IOptionsMonitor<DummyAuthenticationOptions> options,    ILoggerFactory logger,    UrlEncoder encoder) : AuthenticationHandler<DummyAuthenticationOptions>(options, logger, encoder),    IAuthenticationSignOutHandler{    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()    {        await Task.CompletedTask;        var cookie = Request.Cookies.FirstOrDefault(c => c.Key == nameof(DummyAuthenticationHandler));        if (cookie.Value is null)        {            return AuthenticateResult.NoResult();        }        var cookieValue = DecodeFromBase64(cookie.Value);        var claims = JsonSerializer.Deserialize<List<KeyValuePair<string, string>>>(cookieValue);        var claimsIdentity = new ClaimsIdentity(claims!.Select(c => new Claim(c.Key, c.Value)), Scheme.Name);        var ticket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name);        return AuthenticateResult.Success(ticket);    }    protected override Task HandleChallengeAsync(AuthenticationProperties properties)    {        List<KeyValuePair<string, string>> claims =        [            new(OAuthClaim.SubjectId, "__404_user_not_found__"),            new(OAuthClaim.Name, "Middle McMiddleware"),            new(OAuthClaim.Email, "git@gud.commit"),            new(OAuthClaim.JsonTokenIdentifier, "GUID_NOT_FOUND_EXCEPTION"),            new(OAuthClaim.AuthenticationMethodsReferences, "pwd"),            new(OAuthClaim.IdentityProvider, "'; DROP TABLE users; '"),            new(OAuthClaim.PreferredUsername, "git@gud.commit"),            new(OAuthClaim.AuthTime, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()),            new(OAuthClaim.GivenName, "Middle"),            new(OAuthClaim.FamilyName, "McMiddleware"),        ];        var base64Claims = EncodeToBase64(JsonSerializer.Serialize(claims));        Response.Cookies.Append(            nameof(DummyAuthenticationHandler),            base64Claims,            new CookieOptions            {                HttpOnly = true,                Secure = true,                SameSite = SameSiteMode.None,                Path = "/",                Expires = DateTimeOffset.UtcNow.AddDays(1)            }        );        if (properties.RedirectUri is not null)        {            Response.Redirect(properties.RedirectUri);        }        return Task.CompletedTask;    }    public Task SignOutAsync(AuthenticationProperties? properties)    {        Response.Cookies.Delete(nameof(DummyAuthenticationHandler));        if (properties?.RedirectUri is not null)        {            Response.Redirect(properties.RedirectUri);        }        return Task.CompletedTask;    }    private static string EncodeToBase64(string text)    {        var textBytes = Encoding.UTF8.GetBytes(text);        return Convert.ToBase64String(textBytes);    }    private static string DecodeFromBase64(string base64Text)    {        var textBytes = Convert.FromBase64String(base64Text);        return Encoding.UTF8.GetString(textBytes);    }}

CustomAuthenticationStateProvider

public class CustomAuthenticationStateProvider(    IDbContextFactory<MyDbContext> dbFactory) : ServerAuthenticationStateProvider{    public override async Task<AuthenticationState> GetAuthenticationStateAsync()    {        var authState = await base.GetAuthenticationStateAsync();        var claimsUser = authState.User;        // Get your custom data - in this case some roles        var dbUser = await UpsertUser(claimsUser);        // add some new identities to the Claims Principal        claimsUser.AddIdentity(new ClaimsIdentity([            new Claim("user_id", dbUser.UserId.ToString()),        ]));        // return the modified principal        return new AuthenticationState(claimsUser);    }    private async Task<User> UpsertUser(ClaimsPrincipal claimUser)    {        var subjectId = claimUser.Claims.FirstOrDefault(c => c.Type == OAuthClaim.SubjectId)?.Value;        if (string.IsNullOrEmpty(subjectId))        {            throw new ArgumentException("Subject ID is missing from claims");        }        // Get the user from the database        await using var dbContext = await dbFactory.CreateDbContextAsync();        var user = await dbContext.Users            .AsNoTracking()            .FirstOrDefaultAsync(u => u.SubjectId == subjectId);        if (user == null)        {            user = new User            {                SubjectId = subjectId,                FirstName = claimUser.FindFirstValue(OAuthClaim.GivenName) ?? "Unknown",                LastName = claimUser.FindFirstValue(OAuthClaim.FamilyName) ?? "Unknown",                Email = claimUser.FindFirstValue(OAuthClaim.Email) ?? "Unknown",            };            dbContext.Users.Add(user);            await dbContext.SaveChangesAsync();            dbContext.Entry(user).State = EntityState.Detached;        }        return user;    }}

Viewing all articles
Browse latest Browse all 4183

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>