[Blazor] Auth fixes (#20191)

* Adds MetadataAddress property to OidcProviderOptions.
* Sets defaults for the Msal cache location on the provider initialization.
* Updates the template to avoid redirecting to the login page if the user is already authenticated.
* Fixes startup APIs for AddRemoteAuthentication.
* Fixes TryGetToken for the Blazor MSAL library when the token can't be acquired silently.
This commit is contained in:
Javier Calvarro Nelson 2020-03-26 17:07:46 +01:00 committed by GitHub
parent 3e07040bd1
commit b0a95d05e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 113 additions and 55 deletions

View File

@ -92,16 +92,12 @@ class MsalAuthorizeService implements AuthorizeService {
scopes: scopes || this._settings.defaultAccessTokenScopes scopes: scopes || this._settings.defaultAccessTokenScopes
}; };
try { const response = await this._msalApplication.acquireTokenSilent(tokenScopes);
const response = await this._msalApplication.acquireTokenSilent(tokenScopes); return {
return { value: response.accessToken,
value: response.accessToken, grantedScopes: response.scopes,
grantedScopes: response.scopes, expires: response.expiresOn
expires: response.expiresOn };
};
} catch (e) {
return undefined;
}
} }
async signIn(state: any) { async signIn(state: any) {
@ -236,7 +232,7 @@ class MsalAuthorizeService implements AuthorizeService {
// msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state. // msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state.
// The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format // The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format
// simple uses <<base64urlIdentifier>>. // simple uses <<base64urlIdentifier>>.
const appState = !isLogout? this._msalApplication.getAccountState(state[0]): state[0]; const appState = !isLogout ? this._msalApplication.getAccountState(state[0]) : state[0];
const stateKey = `${AuthenticationService._infrastructureKey}.AuthorizeService.${appState}`; const stateKey = `${AuthenticationService._infrastructureKey}.AuthorizeService.${appState}`;
const stateString = sessionStorage.getItem(stateKey); const stateString = sessionStorage.getItem(stateKey);
if (stateString) { if (stateString) {

View File

@ -1,4 +1,5 @@
{ {
"private": true,
"scripts": { "scripts": {
"build": "npm run build:release", "build": "npm run build:release",
"build:release": "webpack --mode production --env.production --env.configuration=Release", "build:release": "webpack --mode production --env.production --env.configuration=Release",

View File

@ -24,7 +24,12 @@ namespace Microsoft.Authentication.WebAssembly.Msal.Models
/// <summary> /// <summary>
/// Gets or sets the msal.js cache options. /// Gets or sets the msal.js cache options.
/// </summary> /// </summary>
public MsalCacheOptions Cache { get; set; } public MsalCacheOptions Cache { get; set; } = new MsalCacheOptions
{
// This matches the defaults in msal.js
CacheLocation = "sessionStorage",
StoreAuthStateInCookie = false
};
/// <summary> /// <summary>
/// Gets or set the list of default access tokens scopes to provision during the sign-in flow. /// Gets or set the list of default access tokens scopes to provision during the sign-in flow.

View File

@ -40,11 +40,6 @@ namespace Microsoft.Authentication.WebAssembly.Msal
} }
options.ProviderOptions.Authentication.NavigateToLoginRequestUrl = false; options.ProviderOptions.Authentication.NavigateToLoginRequestUrl = false;
options.ProviderOptions.Cache = new MsalCacheOptions
{
CacheLocation = "localStorage",
StoreAuthStateInCookie = true
};
} }
public void PostConfigure(string name, RemoteAuthenticationOptions<MsalProviderOptions> options) public void PostConfigure(string name, RemoteAuthenticationOptions<MsalProviderOptions> options)

View File

@ -24,13 +24,21 @@ namespace Microsoft.Extensions.DependencyInjection
/// <returns>The <see cref="IServiceCollection"/>.</returns> /// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddMsalAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure) public static IServiceCollection AddMsalAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
{ {
services.AddRemoteAuthentication<RemoteAuthenticationState, MsalProviderOptions>(); return AddMsalAuthentication<RemoteAuthenticationState>(services, configure);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>()); }
if (configure != null) /// <summary>
{ /// Adds authentication using msal.js to Blazor applications.
services.Configure(configure); /// </summary>
} /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">The <see cref="Action{RemoteAuthenticationOptions{MsalProviderOptions}}"/> to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddMsalAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
{
services.AddRemoteAuthentication<RemoteAuthenticationState, MsalProviderOptions>(configure);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>());
return services; return services;
} }

View File

@ -253,15 +253,21 @@ class OidcAuthorizeService implements AuthorizeService {
export class AuthenticationService { export class AuthenticationService {
static _infrastructureKey = 'Microsoft.AspNetCore.Components.WebAssembly.Authentication'; static _infrastructureKey = 'Microsoft.AspNetCore.Components.WebAssembly.Authentication';
static _initialized = false; static _initialized : Promise<void>;
static instance: OidcAuthorizeService; static instance: OidcAuthorizeService;
public static async init(settings: UserManagerSettings & AuthorizeServiceSettings) { public static async init(settings: UserManagerSettings & AuthorizeServiceSettings) {
// Multiple initializations can start concurrently and we want to avoid that.
// In order to do so, we create an initialization promise and the first call to init
// tries to initialize the app and sets up a promise other calls can await on.
if (!AuthenticationService._initialized) { if (!AuthenticationService._initialized) {
AuthenticationService._initialized = true; this._initialized = (async () => {
const userManager = await this.createUserManager(settings); const userManager = await this.createUserManager(settings);
AuthenticationService.instance = new OidcAuthorizeService(userManager); AuthenticationService.instance = new OidcAuthorizeService(userManager);
})();
} }
await this._initialized;
} }
public static getUser() { public static getUser() {

View File

@ -1,4 +1,5 @@
{ {
"private": true,
"scripts": { "scripts": {
"build": "npm run build:release", "build": "npm run build:release",
"build:release": "webpack --mode production --env.production --env.configuration=Release", "build:release": "webpack --mode production --env.production --env.configuration=Release",

View File

@ -16,6 +16,11 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
/// </summary> /// </summary>
public string Authority { get; set; } public string Authority { get; set; }
/// <summary>
/// Gets or sets the metadata url of the oidc provider.
/// </summary>
public string MetadataUrl { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client of the application. /// Gets or sets the client of the application.
/// </summary> /// </summary>

View File

@ -18,11 +18,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
{ {
private string _message; private string _message;
private RemoteAuthenticationApplicationPathsOptions _applicationPaths; private RemoteAuthenticationApplicationPathsOptions _applicationPaths;
private string _action;
/// <summary> /// <summary>
/// Gets or sets the <see cref="RemoteAuthenticationActions"/> action the component needs to handle. /// Gets or sets the <see cref="RemoteAuthenticationActions"/> action the component needs to handle.
/// </summary> /// </summary>
[Parameter] public string Action { get; set; } [Parameter] public string Action { get => _action; set => _action = value?.ToLowerInvariant(); }
/// <summary> /// <summary>
/// Gets or sets the <typeparamref name="TAuthenticationState"/> instance to be preserved during the authentication operation. /// Gets or sets the <typeparamref name="TAuthenticationState"/> instance to be preserved during the authentication operation.

View File

@ -78,50 +78,83 @@ namespace Microsoft.Extensions.DependencyInjection
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
public static IServiceCollection AddOidcAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure) public static IServiceCollection AddOidcAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure)
{ {
AddRemoteAuthentication<RemoteAuthenticationState, OidcProviderOptions>(services, configure); return AddOidcAuthentication<RemoteAuthenticationState>(services, configure);
}
/// <summary>
/// Adds support for authentication for SPA applications using <see cref="OidcProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
/// </summary>
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{TProviderOptions}"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
public static IServiceCollection AddOidcAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure)
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<OidcProviderOptions>>, DefaultOidcOptionsConfiguration>()); services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<OidcProviderOptions>>, DefaultOidcOptionsConfiguration>());
if (configure != null) return AddRemoteAuthentication<TRemoteAuthenticationState, OidcProviderOptions>(services, configure);
{
services.Configure(configure);
}
return services;
} }
/// <summary> /// <summary>
/// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
/// </summary> /// </summary>
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
public static IServiceCollection AddApiAuthorization(this IServiceCollection services) public static IServiceCollection AddApiAuthorization(this IServiceCollection services)
{ {
var inferredClientId = Assembly.GetCallingAssembly().GetName().Name; return AddApiauthorizationCore<RemoteAuthenticationState>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name);
services.AddRemoteAuthentication<RemoteAuthenticationState, ApiAuthorizationProviderOptions>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>, DefaultApiAuthorizationOptionsConfiguration>(_ =>
new DefaultApiAuthorizationOptionsConfiguration(inferredClientId)));
return services;
} }
/// <summary> /// <summary>
/// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
/// </summary> /// </summary>
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
public static IServiceCollection AddApiAuthorization<TRemoteAuthenticationState>(this IServiceCollection services)
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
{
return AddApiauthorizationCore<TRemoteAuthenticationState>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name);
}
/// <summary>
/// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
/// </summary>
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param> /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
public static IServiceCollection AddApiAuthorization(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure) public static IServiceCollection AddApiAuthorization(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure)
{ {
services.AddApiAuthorization(); return AddApiauthorizationCore<RemoteAuthenticationState>(services, configure, Assembly.GetCallingAssembly().GetName().Name);
}
if (configure != null) /// <summary>
{ /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
services.Configure(configure); /// </summary>
} /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
public static IServiceCollection AddApiAuthorization<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure)
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
{
return AddApiauthorizationCore<TRemoteAuthenticationState>(services, configure, Assembly.GetCallingAssembly().GetName().Name);
}
private static IServiceCollection AddApiauthorizationCore<TRemoteAuthenticationState>(
IServiceCollection services,
Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure,
string inferredClientId)
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
{
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>, DefaultApiAuthorizationOptionsConfiguration>(_ =>
new DefaultApiAuthorizationOptionsConfiguration(inferredClientId)));
services.AddRemoteAuthentication<TRemoteAuthenticationState, ApiAuthorizationProviderOptions>(configure);
return services; return services;
} }

View File

@ -52,8 +52,8 @@ namespace Wasm.Authentication.Server
app.UseRouting(); app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer(); app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>

View File

@ -15,7 +15,14 @@
<Found Context="routeData"> <Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized> <NotAuthorized>
<RedirectToLogin /> @if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized> </NotAuthorized>
</AuthorizeRouteView> </AuthorizeRouteView>
</Found> </Found>

View File

@ -3,6 +3,6 @@
@code { @code {
protected override void OnInitialized() protected override void OnInitialized()
{ {
Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}"); Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
} }
} }

View File

@ -107,12 +107,12 @@ namespace ComponentsWebAssembly_CSharp.Server
app.UseRouting(); app.UseRouting();
#if (OrganizationalAuth || IndividualAuth)
app.UseAuthentication();
#endif
#if (IndividualLocalAuth) #if (IndividualLocalAuth)
app.UseIdentityServer(); app.UseIdentityServer();
#endif #endif
#if (OrganizationalAuth || IndividualAuth)
app.UseAuthentication();
#endif
#if (!NoAuth) #if (!NoAuth)
app.UseAuthorization(); app.UseAuthorization();