Reputation: 121
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
Reputation: 700
Please see this post I made covering how to authenticate the user. A couple things to note:
ClaimsPrincipal
and populate it with
the claims in result.ClaimsPrincipal
_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
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