Skip to content
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

Closed
Eilon opened this issue Mar 22, 2021 · 7 comments
Closed

Blazor Desktop: Support for auth #2529

Eilon opened this issue Mar 22, 2021 · 7 comments
Assignees
Labels
area-blazor Blazor Hybrid / Desktop, BlazorWebView area-docs Conceptual docs, API docs, Samples feature-blazor-auth Issues related to auth in Blazor p/2 Work that is important, but is currently not scheduled for release Task neither bug nor feature but something that needs to be done in support of either
Milestone

Comments

@Eilon
Copy link
Member

Eilon commented Mar 22, 2021

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.

@Eilon
Copy link
Member Author

Eilon commented Apr 20, 2021

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.

@ghost
Copy link

ghost commented Apr 20, 2021

Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. Because it's not immediately obvious that this is a bug in our framework, we would like to keep this around to collect more feedback, which can later help us determine the impact of it. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@Eilon Eilon transferred this issue from dotnet/aspnetcore Sep 16, 2021
@Eilon Eilon added the area-blazor Blazor Hybrid / Desktop, BlazorWebView label Sep 16, 2021
@Eilon Eilon added the feature-blazor-auth Issues related to auth in Blazor label Nov 23, 2021
@mkArtakMSFT
Copy link
Member

This issue tracks the following work:

  • documenting how to enable auth for new Blazor Hybrid apps
  • enable support for MAUI authorization

@javiercn
Copy link
Member

@guardrex here is the draft for this doc. Note that there are still some things we need to resolve:

  • MSAL support for MAUI: I don't think this is ready yet, once it is we will update the links to point to their docs.
  • Authentication with a third-party Open ID provider for MAUI apps:
    • I believe that there should be docs/samples for this somewhere using Xamarin Essentials
  • For winforms/wpf and AAD there should already be docs somewhere, we just need to link to those.
  • For winforms/wpf and any other provider we won't have a link to docs because there is no "blessed" library to do so.

Blazor Hybrid Authentication

Authentication 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 providers

For 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 Hybrid

As part of integrating with Blazor we want to achieve the following goals:

  • Components and services should be able to use the abstractions in the Microsoft.AspNetCore.Components.Authentication package.
    • As a result of this integration, the existing components like AuthorizeView will 'just' work.
  • Components and services should be able to react to changes in the authentication context.
  • Components and services should be able to access any credentials provisioned by the application from the identity provider such as tokens to perform authorized HTTP calls.

Enabling existing authentication to work with Blazor Hybrid applications

Once 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:

  • Install the Microsoft.AspNetCore.Components.Authentication package.
  • Implement your own AuthenticationStateProvider
  • Register your custom AuthenticationStateProvider in the dependency injection container.

Create your own AuthenticationStateProvider

The 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:

  • You can signal the authentication update from outside of the Blazor webview, for example via a global service. In this case, it would be recommended for such service to offer an event that the AuthenticationStateProvider can subscribe to, to in turn, invoke the NotifyAuthenticationStateChanged.
  • The other alternative is to handle all the authentication within the Blazor Webview, in which case, you would add additional methods to the HybridAuthenticationStateProvider that you would use for triggering the login and logout opertations and update the user accordingly.

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:

@inject AuthenticationStateProvider AuthenticationStateProvider

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

@code
{
  public async Task Login()
  {
    await ((HybridAuthenticationStateProvider)AuthenticationStateProvider).LoginAsync();
  }
}

The implementation for the logout step is similar.

Register the services in the container

We need to add the Blazor authorization abstractions to the

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, HybridAuthenticationStateProvider>();
services.AddSingleton<AuthenticatedUser>();

Accessing other authentication information

Blazor 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 app

As 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:

  • Avoid authentication in the context of the webview. This means for example, using a JavaScript oAuth library to perform the authentication flow.
    • In a single page application there is no option to avoid the tokens from being accessible in JavaScript, but native apps don't have that limitation and only being able to access the tokens outside of the browser context means that no rogue third-party script can steal the tokens and compromise your application.
  • Avoid implementing the authentication worflow by yourself. In most cases there are already libraries for the platform that take care of doing this in a secure way, specifically using the system browser instead of a custom webview that can be hijacked.
  • Avoid using the webview control of the platform to perform authentication and rely instead on the system browser when possible.
  • Avoid passing the tokens to the document context (JavaScript). There might be situations where a JavaScript library within the document needs to perform an authorized call to an external service. In such situations, instead of making the token available to JavaScript via JS interop, provide a fake token to the library and within the webview, intercept the outgoing network request and replace the fake token with the real token, making sure to check that the destination of the request is the one you expected.

@guardrex
Copy link
Contributor

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.

@javiercn
Copy link
Member

@guardrex you don't have to rush it :). Don't overwork yourself from something like this, we have plenty of time until Build.

@javiercn
Copy link
Member

Closing this issue as I've already provided the content.

@ghost ghost locked as resolved and limited conversation to collaborators May 29, 2022
@samhouts samhouts added the Task neither bug nor feature but something that needs to be done in support of either label Jul 31, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Blazor Hybrid / Desktop, BlazorWebView area-docs Conceptual docs, API docs, Samples feature-blazor-auth Issues related to auth in Blazor p/2 Work that is important, but is currently not scheduled for release Task neither bug nor feature but something that needs to be done in support of either
Projects
None yet
Development

No branches or pull requests

8 participants