-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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][Wasm] Dynamic and extensible authentication requests #42580
Labels
api-approved
API was approved in API review, it can be implemented
area-blazor
Includes: Blazor, Razor Components
design-proposal
This issue represents a design proposal for a different issue, linked in the description
enhancement
This issue represents an ask for new feature or an enhancement to an existing one
feature-blazor-wasm
This issue is related to and / or impacts Blazor WebAssembly
feature-blazor-wasm-auth
Milestone
Comments
API Review diff and scenariosAPI diff+public enum InteractionType
+{
+ SignIn = 0
+ GetToken = 1
+ SignOut = 2
+}
+ public class InteractiveRequestOptions
+ {
+ public InteractiveRequestOptions();
+ public InteractionType Interaction { get; init; }
+ public string ReturnUrl { get; init; }
+ public System.Collections.Generic.IEnumerable<string> Scopes { get; init; }
+ public System.Collections.Generic.IDictionary<string, object> AdditionalRequestParameters { get; set; }
+
+ public static InteractiveRequestOptions GetToken(string returnUrl);
+ public static InteractiveRequestOptions GetToken(string returnUrl, System.Collections.Generic.IEnumerable<string> scopes);
+ public static InteractiveRequestOptions SignIn(string returnUrl);
+ public static InteractiveRequestOptions SignIn(string returnUrl, System.Collections.Generic.IEnumerable<string> scopes);
+ public static InteractiveRequestOptions SignOut(string returnUrl);
+ }
public class AccessTokenNotAvailableException
{
+ void Redirect(System.Action<InteractiveRequestOptions> request);
}
public class AccessTokenResult
{
+ public AccessTokenResult(AccessTokenResultStatus status, AccessToken token, string interactiveRequestUrl, InteractiveRequestOptions interactiveRequest);
+ public InteractiveRequestOptions InteractiveRequest { get; }
+ public string InteractiveRequestUrl { get; }
}
public class RemoteAuthenticationContext<TRemoteAuthenticationState>
{
+ public InteractiveRequestOptions InteractiveRequest { get; set; }
}
public class RemoteAuthenticationService<TRemoteAuthenticationState, TAccount, TProviderOptions>
{
// Implemented IDisposable with the Dispose pattern
+ protected void virtual Dispose(bool disposing);
// Added an additional constructor overload with a logger parameter
+ public RemoteAuthenticationService(
+ Microsoft.JSInterop.IJSRuntime jsRuntime,
+ Microsoft.Extensions.Options.IOptionsSnapshot<RemoteAuthenticationOptions<TProviderOptions>> options,
+ Microsoft.AspNetCore.Components.NavigationManager navigation, AccountClaimsPrincipalFactory<TAccount> accountClaimsPrincipalFactory,
+ Microsoft.Extensions.Logging.ILogger<RemoteAuthenticationService<TRemoteAuthenticationState, TAccount, TProviderOptions>> logger)
}
+public static class NavigationManagerExtensions()
+{
+ static void NavigateToLogin(this Microsoft.AspNetCore.Components.NavigationManager manager, string loginPath);
+ static void NavigateToLogin(this Microsoft.AspNetCore.Components.NavigationManager manager, string loginPath, InteractiveRequestOptions request);
+ static void NavigateToLogout(this Microsoft.AspNetCore.Components.NavigationManager manager, string logoutPath);
+ static void NavigateToLogout(this Microsoft.AspNetCore.Components.NavigationManager manager, string logoutPath, string returnUrl);
+} ScenariosCustomizing sign in requests.var request = InteractiveRequestOptions.SignIn(Navigation.Uri);
request.AdditionalRequestParameters = new Dictionary<string, object>
{
["prompt"] = "login"
};
Navigation.NavigateToLogin("authentication/login", request); Logging out Navigation.NavigateToLogout("authentication/logout"); Getting a token for a different resource using the IAccessTokenProvidervar accessTokenResult = await AuthorizationService.RequestAccessToken(new AccessTokenRequestOptions
{
Scopes = new[] { "SecondAPI" }
});
if (!accessTokenResult.TryGetToken(out var token))
{
Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, accessTokenResult.InteractiveRequest);
return;
} Getting a token for a different resource as a result of an exception in the authorization message handlertry
{
await httpclient.Get("/orders");
}
catch(AccessTokenNotAvailableException ex)
{
ex.Redirect(requestOptions =>
{
requestOptions.AdditionalRequestParameters.Add("login_hint", "peter@example.com");
});
} |
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
API Review Notes:
+public enum InteractionType
+{
+ SignIn = 0
+ GetToken = 1
+ SignOut = 2
+}
+ public sealed class InteractiveRequestOptions
+ {
+ public InteractiveRequestOptions();
+ public required InteractionType Interaction { get; init; }
+ public required string ReturnUrl { get; init; }
+ public System.Collections.Generic.IEnumerable<string> Scopes { get; init; }
+ public System.Collections.Generic.IDictionary<string, object> AdditionalRequestParameters { get; set; }
+
+ public static InteractiveRequestOptions CreateGetTokenOptions(string returnUrl);
+ public static InteractiveRequestOptions CreateGetTokenOptions(string returnUrl, System.Collections.Generic.IEnumerable<string> scopes);
+ public static InteractiveRequestOptions CreateSignInOptions(string returnUrl);
+ public static InteractiveRequestOptions CreateSignInOptions(string returnUrl, System.Collections.Generic.IEnumerable<string> scopes);
+ public static InteractiveRequestOptions CreateSignOutOptions(string returnUrl);
+ }
public class AccessTokenNotAvailableException
{
+ void Redirect(System.Action<InteractiveRequestOptions> requestOptions);
}
public class AccessTokenResult
{
+ public AccessTokenResult(AccessTokenResultStatus status, AccessToken token, string interactiveRequestUrl, InteractiveRequestOptions interactiveRequest);
+ public InteractiveRequestOptions InteractiveRequest { get; }
+ public string InteractiveRequestUrl { get; }
}
public class RemoteAuthenticationContext<TRemoteAuthenticationState>
{
+ public InteractiveRequestOptions InteractiveRequestOptions { get; set; }
}
public class RemoteAuthenticationService<TRemoteAuthenticationState, TAccount, TProviderOptions>
{
// Added an additional constructor overload with a logger parameter
+ public RemoteAuthenticationService(
+ Microsoft.JSInterop.IJSRuntime jsRuntime,
+ Microsoft.Extensions.Options.IOptionsSnapshot<RemoteAuthenticationOptions<TProviderOptions>> options,
+ Microsoft.AspNetCore.Components.NavigationManager navigation, AccountClaimsPrincipalFactory<TAccount> accountClaimsPrincipalFactory,
+ Microsoft.Extensions.Logging.ILogger<RemoteAuthenticationService<TRemoteAuthenticationState, TAccount, TProviderOptions>> logger)
}
+public static class NavigationManagerExtensions()
+{
+ static void NavigateToLogin(this Microsoft.AspNetCore.Components.NavigationManager manager, string loginPath);
+ static void NavigateToLogin(this Microsoft.AspNetCore.Components.NavigationManager manager, string loginPath, InteractiveRequestOptions requestOptions);
+ static void NavigateToLogout(this Microsoft.AspNetCore.Components.NavigationManager manager, string logoutPath);
+ static void NavigateToLogout(this Microsoft.AspNetCore.Components.NavigationManager manager, string logoutPath, string returnUrl);
+} |
This was referenced Aug 23, 2022
8 tasks
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
api-approved
API was approved in API review, it can be implemented
area-blazor
Includes: Blazor, Razor Components
design-proposal
This issue represents a design proposal for a different issue, linked in the description
enhancement
This issue represents an ask for new feature or an enhancement to an existing one
feature-blazor-wasm
This issue is related to and / or impacts Blazor WebAssembly
feature-blazor-wasm-auth
The new additions offer a loosely coupled model to interact with the underlying authentication service for customizing the authentication details as well as more granular control of the process.
This proposal unblocks:
#37365
#32640
#32782
#19925
#33784
In the initial version of Blazor Webassembly we shipped support for authentication via OAuth supported by oidc-client.js and MSAL.js. Our support allowed people to login, logout and acquire tokens to talk to APIs. All the required parameters were configured statically at startup time.
Over time, we have observed the need from people to dynamically tweak the parameters used for performing these authentication flows. Whether they need to add additional parameters for their provider or pass in additional options to change the login behavior or the behavior when interaction is required to provision a token.
This presented challenges with the current implementation, as all the configuration was defined during startup and there were many options that did not make sense to configure statically, like the login hint or the prompt behavior for the IdP.
In addition to that, adding those options to the existing provider options will add an additional maintenance burden.
In general, there are four scenarios that we want to support:
Out of those flows, we currently only supported 1 and 3, since we defined the configuration during startup and we relied on the ability to get tokens silently indefinitely in other cases thanks to the AAD ability to consent ahead of time.
Unfortunately, there are scenarios when that functionality doesn't work and in addition to that, the latest best pratices recommend not requesting consent for additional scopes until you need them.
Given the desire to enable these two new scenarios and unblock our customers ability to customize the login process as well as the process for acquiring additional tokens interactively, we are making a few changes to the webassembly authentication system, to better support these concerns.
The "webassembly authentication" protocol
Up to now, we would redirect users to the
authentication/login
endpoint and we would optionally include areturnUrl
parameter in the query string, indicating where the user should be sent back when they completed the flow.This proved enough to implement basic functionality where the
AuthorizeView
will work in concert with theRedirectToLogin
component in the template to redirect the user to log in and return back to the page.Similarly, whenever the credentials happened to expire, an exception would be thrown and the user would be redirected to the login endpoint to acquire refreshed credentials and return to the same location.
This approach worked for simple scenarios, but it was not without risks. Passing data through the query string requires us to deal with encoding and decoding the data and can open us to risks like open redirects, etc.
In addition to that, it is hard to pass in structured data (objects) through the query string. There are several approaches, like serializing to JSON and Base64Url encoding the data before putting it on the query string.
To support the new scenarios we care about (customizing the login flow, acquiring tokens interactively, passing in additional data, etc.) we want to allow the developer to pass in parameters to the
authentication/login
endpoint that can flow to the service.Many of those parameters might be request specific and not suitable to be part of the default ProviderOptions we offer.
Similarly, we don't want to necessarily allow changing things like the authority or similar parameters on a per request basis, as that sets up customers for failure.
The "upgraded authentication" protocol
To address some of the challenges of the existing protocol, as well as "standarize" in an approach that can enable additional flexibility for future versions, we are going to introduce a new primitive called the
InteractiveAuthenticationRequest
.This request represents the contract for the
authentication/login
endpoint that the<RemoteAuthenticatorViewCore>
understands.It provides first class support for some of the standard properties that we need for performing the flows, like ReturnUrl and Scopes.
It provides methods to add and retrieve additional parameters that will be passed down to the underlying JS implementation.
In order to pass these parameters to the login endpoint, we are adding support for passing and retrieving state from the
NavigationManager
when performing internal navigations backed by the history API.Leveraging the history API offers several benefits:
authentication/login
endpoint.state
in the history API we can avoid having to deal with encoding and decoding data.state
in the history API reduces the surface attack area, since unlike the query string, it can't be set via a top level navigation nor be influenced from a different origin.state
in the history API removes the need for cleanup as the state is attached to the entry and goes away upon successful login (because we replace the history entry).Passing the data to the login endpoint can be done with an extension method on navigation manager that takes care of serializing the data and putting it on the state parameter when navigating:
With that in mind, the scenarios we care about enabling look like this:
Customize the login process
Customize the options before getting a token interactively
Customize the options when using the IAccessTokenProvider directly
In addition to that, we are going to provide a callback that will be invoked when a given Authentication event happens so that applications can also centralize the logic for passing in additional parameters to the different operations.
That's the reason why we don't expose a
IDictionary<string, object>
as the state is serialized between the location originating the navigation (for example the index page) to theauthentication/login
endpoint and deserialized afterwards, which means that the types in the dictionary are lost (replaced for JsonElement) and that would confuse users.Other information
The text was updated successfully, but these errors were encountered: