Conor Drew
Conor Drew

Reputation: 121

.net maui blazor hybrid MSAL authentication

I am trying to get authentication working though .NET maui blazor for Android, I have set up the manifests to correctly bounce off the AAD and i can get logged in and get my id from azure, the issue is the token isn't working with the Blazor authorisation.

I have followed the solution on this issue on github https://github.com/dotnet/maui/issues/2529 and placed my own hybrid authentication state provider, I have a class authenticated user which holds a ClaimsPrincipal and that is populated when the app is first loaded up, I've used some DI to to set the scoped AuthenticatedUser but its not attaching its self to the authentication state provider

Here is my code so far - this is fired when the app first starts up:

var authService = new AuthService(); // most likely you will inject it in constructor, but for simplicity let's initialize it here
var result = await authService.LoginAsync(CancellationToken.None);
var token = result?.IdToken; // you can also get AccessToken if you need it

if (token != null)
{
    var handler = new JwtSecurityTokenHandler();
    var data = handler.ReadJwtToken(token);
    var claims = data.Claims.ToList();
}

_authenticatedUser.Principal = result.ClaimsPrincipal;

AuthService is:

private readonly IPublicClientApplication authenticationClient;

public AuthService()
{
    authenticationClient = PublicClientApplicationBuilder.Create(Constants.ClientId)
        //.WithB2CAuthority(Constants.AuthoritySignIn) // uncomment to support B2C
        .WithRedirectUri($"msal{Constants.ClientId}://auth")
        .Build();
}

public async Task<AuthenticationResult> LoginAsync(CancellationToken cancellationToken)
{
    AuthenticationResult result;

    try
    {
        result = await authenticationClient
                .AcquireTokenInteractive(Constants.Scopes)
                .WithAuthority("[TENANT ID HERE]")
                .WithPrompt(Prompt.ForceLogin)
#if ANDROID
                .WithParentActivityOrWindow(Platform.CurrentActivity)

#endif
                .ExecuteAsync(cancellationToken);
        return result;
    }
    catch (MsalClientException)
    {
        return null;
    }
}

And constants just holds the Client id.

So the app starts, it redirects to sign in, gets the token and gets a JWT and claims, then sets _authenticatedUser.Principal to this claim.

My HybridStateAuthenticator looks like this:

public class HybridAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> _authenticationState;
    public HybridAuthenticationStateProvider(AuthenticatedUser user) =>
    _authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
    _authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; }
}

What I'm asking is how do I attach this Stateprovider to the Maui Blazor and then use authorization view to get the context identity

Upvotes: 3

Views: 3655

Answers (2)

Anil
Anil

Reputation: 700

Please see this post I made covering how to authenticate the user. A couple things to note:

  • You'll need to create a new ClaimsPrincipal and populate it with the claims in result.ClaimsPrincipal
  • I left out the user wrapper, but you could easily replace _currentUser in the example code with an instance of AuthenticatedUser

As for attaching the state provider and using an AuthorizationView, nothing really changes with MAUI in the mix.

First, we'll need to update Main.razor to handle the logic for checking whether the user is authenticated when they visit a page:

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Main).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <Authorizing>
                    Authorizing...
                </Authorizing>
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p>You are not authorized to access this resource.</p>
                    }                   
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>      
            <LayoutView Layout="@typeof(MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>       
        </NotFound>
    </Router>
</CascadingAuthenticationState>

LoginDisplay.razor is a component that can be attached to MainLayout.razor to display the login status and the logout button:

@using Microsoft.AspNetCore.Components.Authorization

@inject AuthenticationStateProvider AuthenticationStateProvider

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity?.Name!
        <button class="nav-link btn btn-link" @onclick="Logout">Log out</button>
    </Authorized>
</AuthorizeView>

@code {
    private void Logout()
    {
        //TODO: Initiate logout with state provider
    }
}

RedirectToLogin.razor is used to redirect users who have not logged in back to the login page:

@inject NavigationManager NavigationManager

<div class="loader loader-bouncing"><span>Redirecting...</span></div>

@code {
    protected override void OnInitialized()
    {
        NavigationManager.NavigateTo("/login");
    }
}

The login page:

@page "/login"

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

@inject AuthenticationStateProvider AuthenticationStateProvider
@inject NavigationManager NavigationManager

@attribute [AllowAnonymous]

<button @onclick="PromptLogin">Log in</button>

@code
{
    public async Task PromptLogin()
    {
        //TODO: Tnitiate a login using the state provider here

        NavigationManager.NavigateTo("/");
    }
}

From here you can continue adorning your pages with the Authorize attribute.

Upvotes: 0

StefanoM5
StefanoM5

Reputation: 1337

I'm triyng to integrate this github solution to my login logic https://github.com/mitchelsellers/dotnet-maui-blazor-customauthentication-sample it's very useful guide.

Upvotes: 1

Related Questions