This sample demonstrates how to build .NET MAUI Blazor Hybrid and Web Apps that shares common UI and also provides authentication. It uses ASP.NET Core Identity local accounts but you can use this pattern for any authentication provider you need to call from a MAUI Blazor Hybrid client.
- Clone the repository.
- Make sure you have .NET 9 installed and the MAUI workload.
- Open the solution in Visual Studio 2022 or VS Code with the .NET MAUI extension installed.
- Set the
MauiHybridAuth
MAUI project as the startup project. - Start the
MauiHybridAuth.Web
project without debugging (in Visual Studio right-click on the project and select "Debug -> Start without Debugging"). - Register a user in the Blazor Web app UI or navigate to
https://localhost:7157/swagger
in your browser to pull up the identity endpoints and register a user using the/Register
endpoint. - Start (F5) the
MauiHybridAuth
MAUI project. You can set the debug target to Windows, or an Android device or emulator. - Notice you can only see the Home and Login pages.
- Log in with the user you registered.
- Notice you can now see the shared Counter and Weather pages.
- Log out and notice you can only see the Home and Login pages again.
- Navigate the web app to
https://localhost:7157/
and the app will behave the same.
The shared UI is in the MauiHybridAuth.Shared
project. This project contains the Razor components that are shared between the MAUI and Blazor Web projects (Home, Counter and Weather pages). The Counter.razor
and Weather.razor
pages are protected by the [Authorize]
attribute so you cannot navigate to them unless you are logged in.
@page "/counter"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
The Routes.razor
uses the AuthorizeRouteView
to route users appropriately based on their authentication status. If a user is not authenticated, they are redirected to the Login
page.
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
<Authorizing>
Authorizing...
</Authorizing>
<NotAuthorized>
<Login />
</NotAuthorized>
</AuthorizeRouteView>
The NavManu.razor
components contain the navigation menu that uses AuthorizationView
to show/hide links based on the user's authentication status.
<AuthorizeView>
<NotAuthorized>
<!-- Navlinks that display when not logged in -->
</NotAuthorized>
<Authorized>
<!-- Navlinks that display when logged in -->
</Authorized>
</AuthorizeView>
The Blazor Web app contains all the pages and uses the SignInManager
framework class to manage logins and users. All of this is generated automatically when you create a Blazor Web project and select to use Authentication with Individual accounts. In order for the MAUI client (or any external client) to authenticate, the ASP.NET Identity endpoints need to be exposed. In the Program.cs
file this is set up with the call to AddIdentityEnpoints
and MapIdentityApi
. NOTE: You'll need to remove the generated call to .AddIdentityCookies
on .AddAuthentication
. It is not necessary when calling .AddIdentityEndpoints
and will result in an error.
// Add Auth services used by the Web app
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
//Needed for external clients to log in
builder.Services.AddIdentityApiEndpoints<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
//Register needed elements for authentication:
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
var app = builder.Build();
//Needed for external clients to log in
app.MapIdentityApi<ApplicationUser>();
The Login.razor
page is where the user logs in. The Login
page injects the ICustomAuthenticationStateProvider
and uses the AuthenticationStateProvider
to authenticate the user and redirect them to the home page if successful. When the state changes, the AuthorizeView
reacts and will show the appropriate pages/links based on the user's authentication status.
//Called on valid submit
private async Task LoginUser()
{
await AuthStateProvider.LogInAsync(LoginModel);
if (AuthStateProvider.LoginStatus != LoginStatus.Success)
{
//Show error message
loginFailureHidden = false;
return;
}
Navigation.NavigateTo(""); //Root URL
}
NOTE: This sample only implements Login and Logout pages on the MAUI client but you could build Register and other management pages against the exposed endpoints for more functionality. For more information on identity endpoints see How to use Identity to secure a Web API backend for SPAs
The ICustomAuthenticationStateProvider
interface is implemented by the MauiAuthenticationStateProvider
class in the MauiHybridAuth
MAUI project. This class is responsible for managing the user's authentication state and providing the AuthenticationState
to the app. The MauiAuthenticationStateProvider
class uses the HttpClient
to make requests to the server to authenticate the user. See the official documentation for more information on ASP.NET Core Blazor Hybrid authentication and authorization.
public interface ICustomAuthenticationStateProvider
{
public LoginStatus LoginStatus { get; set; }
Task<AuthenticationState> GetAuthenticationStateAsync();
Task LogInAsync(LoginModel loginModel);
void Logout();
}
This class also handles calling localhost via the emulators and simulators for easy testing. See the official documentation for information on what you need to do to be able to call local services from emulators and simulators. This class could also use the SecureStorage
API to store the user's token securely on the device, or handle any other platform specific functionality if needed.
The MAUI project's MauiProgram.cs
file is where the MauiAuthenticationStateProvider
is registered with the DI container. It also needs to register the Authorization core components where things like AuthorizeView
are defined.
// This is the core functionality
builder.Services.AddAuthorizationCore();
// This is our custom provider
builder.Services.AddScoped<ICustomAuthenticationStateProvider, MauiAuthenticationStateProvider>();
// Use our custom provider when the app needs an AuthenticationStateProvider
builder.Services.AddScoped<AuthenticationStateProvider>(s
=> (MauiAuthenticationStateProvider)s.GetRequiredService<ICustomAuthenticationStateProvider>());