0

I have the similar code working in previous version like .NET 7, but I get "HTTP ERROR 401" error, when I add @attribute [Authorize] to the page.

Here is the sample project in GIT hub.

https://github.com/sbakula/BlazorServerCustomAuthTest

My Program.cs code;

using BlazorServerCustomAuthTest.Auth;
using BlazorServerCustomAuthTest.Components;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
            .AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidAudience = "XXXX",
                    ValidIssuer = "XXXX",
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("XXXX"))
                };
            });

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
builder.Services.AddScoped<AuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>(
                provider => provider.GetRequiredService<AuthStateProvider>()
                );
builder.Services.AddScoped<ILoginService, AuthStateProvider>(
                provider => provider.GetRequiredService<AuthStateProvider>()
                );

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.UseAntiforgery();

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

app.Run();

Routes.razor code:

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
            <NotAuthorized>
                <text>Custom not authorized...</text>
            </NotAuthorized>
        </AuthorizeRouteView>
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>

My AuthStateProvider.cs:

using Microsoft.AspNetCore.Components.Authorization;
using System.Net.Http.Headers;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using BlazorServerCustomAuthTest.Models;

namespace BlazorServerCustomAuthTest.Auth

{
    public class AuthStateProvider : AuthenticationStateProvider, ILoginService
    {
        private ProtectedSessionStorage ProtectedSessionStore;

        static AuthenticationState Anonymous =>
            new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));

        public AuthStateProvider(ProtectedSessionStorage ProtectedSessionStore)
        {
            this.ProtectedSessionStore = ProtectedSessionStore;
        }

        public async override Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            UserProfile? user;
            var result = await ProtectedSessionStore.GetAsync<UserProfile>("UserProfile");

            if (result.Success)
            {
                user = result.Value;
                return await Task.FromResult(BuildAuthenticationState(user));
            }
            else
            {
                return Anonymous;
            }
        }

        public async Task Login(UserProfile user)
        {
            await ProtectedSessionStore.SetAsync("UserProfile", user);
            var authState = BuildAuthenticationState(user);
            NotifyAuthenticationStateChanged(Task.FromResult(authState));
        }

        public async Task Logout()
        {
            await ProtectedSessionStore.DeleteAsync("UserProfile");
            NotifyAuthenticationStateChanged(Task.FromResult(Anonymous));
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
        }

        static AuthenticationState BuildAuthenticationState(UserProfile? userProfile)
        {
            if (userProfile is null)
            {
                return Anonymous;
            }
            else
            {
                var claims = new List<Claim> { };
                claims.Add(new Claim(ClaimTypes.Name, userProfile.UserName));
                return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")));
            }
        }

    }
}

ILoginService.cs:

using BlazorServerCustomAuthTest.Models;

namespace BlazorServerCustomAuthTest.Auth
{
    public interface ILoginService
    {
        Task Login(UserProfile user);
        Task Logout();
    }
}

_Imports.razor:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorServerCustomAuthTest
@using BlazorServerCustomAuthTest.Components
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

App.razor:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="BlazorServerCustomAuthTest.styles.css" />
    <HeadOutlet @rendermode="new InteractiveServerRenderMode( prerender: false )" />
</head>

<body>
    <Routes @rendermode="new InteractiveServerRenderMode( prerender: false )" />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

1 Answer 1

0

I trust you are trying to integrate a custom authentication mechainsm so that when there's no user signed in the blazor app, it will redirect to the login page. But what you did in your Program.cs is adding JWT auth which will require a token in the request header otherwize returning 401 error.

I modifty the codes li'ke below

.AddJwtBearer(options =>
    {
        options.SaveToken = true;
        options.RequireHttpsMetadata = false;
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidAudience = "Test.com",
            ValidIssuer = "Test.com",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"))
        };
    });

and generate a jwt token to send a Get request to the home page, I can get 200 code and html content response.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidXNlcm5hbWUxIiwianRpIjoiMTg2MmIyYjMtMDhiYy00ZDExLThkNDctOWRiNTExMjEzMzYzIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzI3ODU4OTQ1LCJpc3MiOiJUZXN0LmNvbSIsImF1ZCI6IlRlc3QuY29tIn0.7oXqbUCX8iumocwP0dAlIeNruaofaRK_3QqwLL-PHT0

enter image description here

What you should have is a custom AuthenticationStateProvider. You can take a look at this tutorial to add login component and in that component, then call NotifyAuthenticationStateChanged method to update the authentication state.

Sign up to request clarification or add additional context in comments.

3 Comments

I find a related case here, which shall be helpful for you.
This works, but the authentication is not persistent across pages. It also looses on page refresh and errors with 401 code.
Thank you for your kind update about your test result. I had a test with the asp.net core identity in blazor server app which could work well. That sample project is created by VS 2022 template by choosing the authentication type as Individual Account. In that case, we could see it calls built-in SignInAsycn method which would also create cookie to persist the sign in state. You can take a look at the Comment in the case I shared above, it shared a complete sample in github which also used cookie authentication.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.