1

I have a complete custom implementation of JWT auth for Blazor Wasm + Web Api. I would like to make it work with Blazor Server (with the proper modifications).

1, Startup/Program

// I tried with every combinations
//services.AddAuthenticationCore();
//services.AddAuthorizationCore();

//services.AddAuthentication();
//services.AddAuthorization();

services.AddScoped<CustomAuthenticationStateProvider>();
services.AddScoped<ServerAuthenticationStateProvider>(provider =>
    provider.GetRequiredService<CustomAuthenticationStateProvider>());

...

app.UseRouting();

//app.UseAuthentication();
//app.UseAuthorization();

app.MapBlazorHub();

2, App.razor

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <div class="d-flex flex-column mx-auto">
                        <h1 class="mx-auto">Sorry</h1>
                        <p class="mx-auto">You're not authorized to reach this resource.</p>
                        <p class="mx-auto">Please log in or return to the index page</p>

                        <UnauthorizedNavigation />
                    </div>
                </NotAuthorized>
                <Authorizing>
                    <h1>Authentication in progress</h1>
                </Authorizing>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>  

3, CustomAuthenticationStateProvider implementation

public class CustomAuthenticationStateProvider : ServerAuthenticationStateProvider
{
    private readonly ILocalTokenService _localTokenService;
    private readonly JwtTokenService _jwtTokenService;

    #region Constructor & Initialization

    public CustomAuthenticationStateProvider(ILocalTokenService localTokenService, JwtTokenService jwtTokenService)
    {
        _localTokenService = localTokenService;
        _jwtTokenService = jwtTokenService;
    }

    #endregion

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        await Task.Delay(1000);
        Debug.WriteLine("GetAuthenticationStateAsync");

        try
        {
                // ...Code...

                var identity = new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.NameIdentifier, userId),
                    new Claim(ClaimTypes.Name, userName)
                }, auth_type);

                var principal = new ClaimsPrincipal(identity);
                var authTask = Task.FromResult(new AuthenticationState(principal));
                return await authTask;
            }
            else
            {
                Debug.WriteLine("Empty");
                return await ReturnEmptyAuthState();
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e);
            _logger.LogError(e.Message);
            return await ReturnEmptyAuthState();
        }
    }

    private async Task<AuthenticationState> ReturnEmptyAuthState()
    {
        var identityEmpty = new ClaimsIdentity();
        var principalEmpty = new ClaimsPrincipal(identityEmpty);
        var authTaskEmpty = Task.FromResult(new AuthenticationState(principalEmpty));
        return await authTaskEmpty;
    }

    public async Task<bool> MarkUserAsAuthenticated(string jwtToken, string refreshToken)
    {
        try
        {
            // ...Code...

            var identity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, userId),
                new Claim(ClaimTypes.Name, userName)
            }, auth_type);

            var userclaim = new ClaimsPrincipal(identity);
            var task = Task.FromResult(new AuthenticationState(userclaim));

            SetAuthenticationState(task);
            //NotifyAuthenticationStateChanged(task);
            return true;
        }
        catch (Exception e)
        {
            _logger.LogError(e.Message);
            return false;
        }
    }

    public async Task MarkUserAsLoggedOut()
    {
        // ...Code...
        SetAuthenticationState(task);
        //NotifyAuthenticationStateChanged(task);
    }
}

Issues (after successfull login!):

a, I created an AuthTestPage with @attribute [Authorize] - it always displays as not authorized

b, Using AuthorizeView - Authorized/NotAuthorized parts - same, the Authorized part is never triggered

The source of the issue I guess that the CustomAuthenticationStateProvider.GetAuthenticationStateAsync is never triggered by the framework for some reason. It should be called at least on new page navigation or by opening the app in browser I suspect.

What do you think, what am I missing, or it is a bad approach with Blazor Server what I try to achieve?

István Piroska
  • 214
  • 3
  • 15

0 Answers0