-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blazor Desktop: Support for auth #2529
Comments
For Azure-based auth this should "just work" using existing APIs. On .NET MAUI you should be able to use Xamarin.Essentials, and on WPF/WinForms this should work with MSAL.NET. We need to verify that these scenarios work as expected on all platforms. |
Thanks for contacting us. |
This issue tracks the following work:
|
@guardrex here is the draft for this doc. Note that there are still some things we need to resolve:
Blazor Hybrid AuthenticationAuthentication on Blazor Hybrid applications is handled by the native platform libraries, as they offer enhanced security guarantees that the browser sandbox can not offer. Authentication of native applications happens via an OS specific mechanism or via a federated protocol like Open ID. We recommend you start by following your Identity Provider guidance on how to add authentication to your application; as the process of further integrating with Blazor only adds a few more extra steps that we describe below. Integration with different identity providersFor integrating your app authentication with Azure Active Directory, Azure Active Directory B2C, or other Open ID providers check out the following resources:
Integrating authentication with Blazor HybridAs part of integrating with Blazor we want to achieve the following goals:
Enabling existing authentication to work with Blazor Hybrid applicationsOnce you have added authentication to your Maui, WPF or Windows Forms application and are able to log in and log out successfully you can integrate with Blazor to make the authenticated user available to your Blazor Hybrid components and services. To do so you need to perform three steps:
Create your own AuthenticationStateProviderThe AuthenticationStateProvider is the abstraction that Blazor components use to access information about the current authenticated user as well as to receive updates when the authentication state changes. If your app authenticates the user immediately after the application launches and the authenticated user remains the same for the entirety of the application lifetime, you don't have to worry about notifying updates and only need to provide the information about the currently authenticated user. Think, for example of an application that asks you to log in when you open it and that when you log out, displays the login screen again. In this case the implementation is Trivial: public 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; }
} If you need to update the user while the Blazor application is running, you can do so by calling NotifyAuthenticationStateChanged within the AuthenticationStateProvider implementation. In this scenario, there are two options:
Option 1 would be something like this: public HybridAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly AuthenticatedUser _authenticatedUser;
private Task<AuthenticationState> _authenticationState;
public HybridAuthenticationStateProvider(AuthenticatedUser user)
{
_authenticationState = Task.FromResult(new AuthenticationState(user.Principal));
_authenticatedUser = user;
_authenticatedUser.UserChanged += () =>
{
_authenticationState = Task.FromResult(new AuthenticationState(user.Principal));
NotifyAuthenticationStateChanged(_authenticatedUser);
}
}
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
_authenticationState;
}
public class AuthenticatedUser
{
public ClaimsPrincipal Principal { get; set; }
public event Action UserChanged;
} From anywhere in the app, we can resolve the AuthenticatedUser service after we have authenticated the user and set the principal property to the authenticated user before we start the Blazor application. var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();
authenticatedUser.Principal = currentUser. As an alternative, you can set the user on Thread.CurrentThread.Principal and use that instead of setting the user via a service, which avoids having to deal with the dependency injection container. Option 2 would be something like this: public HybridAuthenticationStateProvider : AuthenticationStateProvider
{
private Task<AuthenticationState> _currentUser = Task.FromResult(new ClaimsPrincipal(new ClaimsIdentity()));
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
_currentUser;
public Task LoginAsync()
{
// Trigger the login process
var loginTask = CreateLoginTask;
// Notify that an update is in progress
// This allows the app to provide a temporary UI while the login process is in progress.
NotifyAuthenticationStateChanged(loginTask);
// Return the task so that the component that triggered the login can await and react
// afterwards.
return loginTask;
Task<AuthenticationState> CreateLoginTask()
{
// This is the part that you fill with your actual login implementation
var newUser = await LoginWithIdentityProviderAsync();
_currentUser = new AuthenticationState(newUser);
return _currentUser;
}
}
} On your LoginComponent you would do something like this:
The implementation for the logout step is similar. Register the services in the containerWe need to add the Blazor authorization abstractions to the services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, HybridAuthenticationStateProvider>();
services.AddSingleton<AuthenticatedUser>(); Accessing other authentication informationBlazor doesn't define an abstraction to deal with other credentials like AccessTokens to use for HTTP requests to APIs, etc. The recomendation in these cases is that you follow the guidance from your identity provider to manage the credentials directly with the primitives your SDK provides. It is very common to have some sort of token store that keeps the credentials stored in the device, and if that primtive is registered on the dependency injection container, you can consume it within your app. Blazor is not aware of your credentials nor interacts with them in any way, so you are free to follow whaterver approach you deem most convenient. Other security considerations when implementing authentication within your appAs we mentioned the authentication process is external to Blazor and you should follow the guidance your Identity Provider offers. However we want to emphasize some things that you should avoid:
|
I'll have that up in a PR no later than Monday morning 9am CST. I've opened a doc issue at Blazor Hybrid security guidance (dotnet/AspNetCore.Docs #25771) to track the work and will ping u on the PR when it's ready for review. |
@guardrex you don't have to rush it :). Don't overwork yourself from something like this, we have plenty of time until Build. |
Closing this issue as I've already provided the content. |
Scenario: App wants to provide login support using either custom auth or OS-based auth functionality.
Blazor already has some auth concepts, so we'll want to integrate those with what desktop/mobile apps need to do for auth. Some platforms have strict auth requirements for either browser-based auth, or using specific auth protocols.
The text was updated successfully, but these errors were encountered: