Blazor web app net 8 rendermode auto authentication

Ángel Rubén Valdeolmos Carmona 586 Reputation points
2024-04-26T09:26:54.0033333+00:00

I am confused on how to add authorization to my components.

In the client part I am using AuthenticationStateProvider and in the server part ServerAuthenticationStateProvider. Segum I've been looking at the documentation and other examples.

My blazor app doesn't use individual accounts. I log in to my api and get a token, how do I tell it that I am already authenticated in the AuthenticationStateProvider and ServerAuthenticationStateProvider providers?

so the state behaves, or is this not so? how do I proceed? Server:

public class PersistingAuthenticationStateProvider : ServerAuthenticationStateProvider, IDisposable
{
    private Task<AuthenticationState>? _authenticationStateTask;
    private readonly PersistentComponentState _state;
    private readonly PersistingComponentStateSubscription _subscription;
    private readonly IdentityOptions _options;
    public PersistingAuthenticationStateProvider(
        PersistentComponentState persistentComponentState,
        IOptions<IdentityOptions> optionsAccessor)
    {
        _options = optionsAccessor.Value;
        _state = persistentComponentState;
        AuthenticationStateChanged += OnAuthenticationStateChanged;
        _subscription = _state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);
    }
    private async Task OnPersistingAsync()
    {
        if (_authenticationStateTask is null)
        {
            throw new UnreachableException($"Authentication state not set in {nameof(OnPersistingAsync)}().");
        }
        var authenticationState = await _authenticationStateTask;
        var principal = authenticationState.User;
        if (principal.Identity?.IsAuthenticated == true)
        {
            var userId = principal.FindFirst(_options.ClaimsIdentity.UserIdClaimType)?.Value;
            var role = principal.FindFirst(_options.ClaimsIdentity.RoleClaimType)?.Value;
            if (userId != null && role != null)
            {
                _state.PersistAsJson(nameof(UserInfoResponse), new UserInfoResponse
                {
                    Id = Convert.ToInt32(userId),
                    IdProfile = Convert.ToInt32(role),
                    Email = "",
                    Identification = "",
                    Name = "",
                    Phone = ""
                });
            }
        }
    }
    private void OnAuthenticationStateChanged(Task<AuthenticationState> authenticationStateTask)
    {
        _authenticationStateTask = authenticationStateTask;
    }
    public void Dispose()
    {
        _authenticationStateTask?.Dispose();
        _subscription.Dispose();
        AuthenticationStateChanged -= OnAuthenticationStateChanged;
    }
}

Client:

public class PersistentAuthenticationStateProvider : AuthenticationStateProvider
{
    private static readonly Task<AuthenticationState> DefaultUnauthenticatedTask =
        Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
    private readonly Task<AuthenticationState> _authenticationStateTask = DefaultUnauthenticatedTask;
    public PersistentAuthenticationStateProvider(PersistentComponentState state)
    {
        if (!state.TryTakeFromJson<UserInfoResponse>(nameof(UserInfoResponse), out var userInfoResponse) || userInfoResponse is null)
        {
            return;
        }
        var claims = new List<Claim>()
        {
            new Claim(ClaimTypes.NameIdentifier, userInfoResponse.Id.ToString()),
            new Claim(ClaimTypes.Role, "Administrador")
        };
        _authenticationStateTask = Task.FromResult(
            new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims,
                authenticationType: nameof(PersistentAuthenticationStateProvider)))));
    }
    public override Task<AuthenticationState> GetAuthenticationStateAsync() => _authenticationStateTask;
}

Login:

            var claims = new List<Claim>()
            {
             new Claim(ClaimTypes.NameIdentifier, userInfo.Id.ToString()),
                new Claim(ClaimTypes.Role, "Administrador")
            };
            var claimPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "profile"));
            ((PersistingAuthenticationStateProvider)authenticationStateProvider).SetAuthenticationState(Task.FromResult(new AuthenticationState(claimPrincipal)));

Then I have other components with

<AuthorizeView>
     <Authorized>
         <Component>
     </Authorized>
</AuthorizeView>

But I never log in, because I lose authentication with the providers.

Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,401 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Bruce (SqlWork.com) 56,846 Reputation points
    2024-04-26T16:27:15.2133333+00:00

    you don't give enough information. after pre-render are you using server mode or client mode? do you need a bearer token after authentication to make api calls?

    if you are user blazor server and don't need a bearer token, then the cookie authentication should work. just configure as you would for non-blazer app, and build custom claim in the create cookie. the default blazor authenication state provider will load from the HttpContext.

    if you need bearer tokens then it is a lot more work. the pre-render needs to write the access & refresh tokens to persisted state:

    https://learn.microsoft.com/en-us/aspnet/core/blazor/components/prerender?view=aspnetcore-8.0

    then you need a custom authentication state provider that loads from the persisted state. it should also save the token, and support getting a new access token from the refresh token, when the access token expires. it should also parse the token to get the user and claims.

    if you are using WASM and cookie authentication, then the you just need to save the user and claims to re-render state.

    to login the blazor site needs to redirect to the oauth site, which will unload the blazor app. when redirected back to the blazor site, a pre-render blazor app will be created, run and unload. this pre-render should detect the tokens and write to persisted render state. when the browser loads the pre-render response, it will load a new blazor instance. this new instance should read the pre-render state.

    0 comments No comments

  2. Ángel Rubén Valdeolmos Carmona 586 Reputation points
    2024-04-27T07:23:38.6533333+00:00

    I am trying to authenticate via cookie without success:

    Program.cs blazor server:

            builder.Services.AddAuthentication("Auth")
                .AddCookie("Auth", options =>
                {
                    options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
                    options.SlidingExpiration = true;
                    options.LoginPath = "/login";
                });
            //Auth
            builder.Services.AddCascadingAuthenticationState();
            builder.Services.AddScoped<AuthenticationStateProvider, PersistingAuthenticationStateProvider>();
    

    Controller:

        [HttpGet]
        public async Task<IActionResult> Auth()
        {
            var claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.NameIdentifier, "1"),
                    new Claim(ClaimTypes.Role, "2")
                };
            var claimPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Auth"));
            await HttpContext.SignInAsync("Auth", claimPrincipal);
            return Redirect("/");
        }
    

    login.razor:

    @page "/login"
    @rendermode @(new InteractiveServerRenderMode(prerender: false))
    @inject HttpClient httpClient
    <button class="btn btn-primary" @onclick="IncrementCount">Login</button>
    @code {
        private async void IncrementCount()
        {
            httpClient.BaseAddress = new Uri(" http://localhost:5021");
            await httpClient.GetAsync("/api/auth");
        }
    

    The cookie is never created