How to use a blazor server app with a web api for authentication and persist authentication across circuits?

Andrew White 0 Reputation points
2025-03-30T22:59:33.5866667+00:00

I originally posted a comment under this question as I am following a similar scenario:
https://learn.microsoft.com/en-us/answers/questions/1454927/how-to-use-a-blazor-server-app-with-a-web-api-for

I have a custom AuthenticationStateProvider which is correctly updated once my login request to the web API is returned, and the user claims and state is persists throughout the circuit lifetime.

However, when I refresh the web page, and a new circuit is established, the user is redirected to the login page for authentication, even though querying the AuthenticationStateProvider returns the correct user details/claims etc.

How do I get the new circuit to persist the authenticated user state?

Developer technologies .NET Blazor
{count} votes

2 answers

Sort by: Most helpful
  1. Bruce (SqlWork.com) 77,686 Reputation points Volunteer Moderator
    2025-03-31T16:08:04.1866667+00:00

    unless you wrote code to save and restore the AuthenticationStateProvider state to a persistent store, it is lost on a page refresh, as the blazor state is tied to the circuit. though usually you use the the authenticated users id as a key to the persistent store, so it does not work for authentication data.

    to support refresh, you typically (like the default behavior) store the credentials in a cookie. for blazor code to set a cookie, it requires a reload (say via js interop) as you need a browser redirect. you can also use local storage, this is common for a refresh token

    0 comments No comments

  2. Anonymous
    2025-04-01T09:58:06.58+00:00

    Hi,@Andrew White

    Since you are also using the scheme IdentityConstants.ApplicationScheme wthich is based on cookie protecting your Blazor app

    builder.Services .AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; }) .AddIdentityCookies();
    

    In your scenario, you could send request to login endpoint with query parameter useCookies=true ,

    then the cookie would be generated in response

    The source codes of login endpoint:

    routeGroup.MapPost("/login", async Task<Results<Ok<AccessTokenResponse>, EmptyHttpResult, ProblemHttpResult>>
        ([FromBody] LoginRequest login, [FromQuery] bool? useCookies, [FromQuery] bool? useSessionCookies, [FromServices] IServiceProvider sp) =>
    {
        var signInManager = sp.GetRequiredService<SignInManager<TUser>>();
        var useCookieScheme = (useCookies == true) || (useSessionCookies == true);
        var isPersistent = (useCookies == true) && (useSessionCookies != true);
        signInManager.AuthenticationScheme = useCookieScheme ? IdentityConstants.ApplicationScheme : IdentityConstants.BearerScheme;
        var result = await signInManager.PasswordSignInAsync(login.Email, login.Password, isPersistent, lockoutOnFailure: true);
        if (result.RequiresTwoFactor)
        {
            if (!string.IsNullOrEmpty(login.TwoFactorCode))
            {
                result = await signInManager.TwoFactorAuthenticatorSignInAsync(login.TwoFactorCode, isPersistent, rememberClient: isPersistent);
            }
            else if (!string.IsNullOrEmpty(login.TwoFactorRecoveryCode))
            {
                result = await signInManager.TwoFactorRecoveryCodeSignInAsync(login.TwoFactorRecoveryCode);
            }
        }
        if (!result.Succeeded)
        {
            return TypedResults.Problem(result.ToString(), statusCode: StatusCodes.Status401Unauthorized);
        }
        // The signInManager already produced the needed response in the form of a cookie or bearer token.
        return TypedResults.Empty;
    });
    

    The codes to read the cookie and append it to response in static server render mode:

    CookieContainer cookies = new CookieContainer();
    HttpClientHandler handler = new HttpClientHandler();
    handler.CookieContainer = cookies;
    var client = new HttpClient(handler);
    var response = await client.PostAsJsonAsync("{BaseUrl}/login?useCookies=true", Input);
    if(response.StatusCode == HttpStatusCode.OK)
    {
       
        var respCookies = cookies.GetCookies(new Uri("{BaseUrL}"));
        var authCookie = respCookies.Where(x => x.Name == ".AspNetCore.Identity.Application").FirstOrDefault();
            if (authCookie != null)
            {
            HttpContext.Response.Cookies.Delete(".AspNetCore.Identity.Application");
            HttpContext.Response.Cookies.Append(".AspNetCore.Identity.Application", authCookie.Value, new CookieOptions()
                    {
                        Domain = authCookie.Domain,
                        HttpOnly = authCookie.HttpOnly,
                        SameSite = SameSiteMode.None,
                        Path = authCookie.Path,
                        Secure = authCookie.Secure,
                        Expires = authCookie.Expires,
                    });
            }
            RedirectManager.RedirectTo(ReturnUrl);
    }
    

    In interactive render mode,you could set the cookie with js codes

    and share the cookie between two apps by register the service in both projects:

    builder.Services.AddDataProtection()
        .SetApplicationName("SameAppName");
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards, Ruikai Feng


Your answer

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