[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
};
try {
const response = await this._msalApplication.acquireTokenSilent(tokenScopes);
return {
value: response.accessToken,
grantedScopes: response.scopes,
expires: response.expiresOn
};
} catch (e) {
return undefined;
}
const response = await this._msalApplication.acquireTokenSilent(tokenScopes);
return {
value: response.accessToken,
grantedScopes: response.scopes,
expires: response.expiresOn
};
}
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.
// The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format
// 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 stateString = sessionStorage.getItem(stateKey);
if (stateString) {

View File

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

View File

@ -24,7 +24,12 @@ namespace Microsoft.Authentication.WebAssembly.Msal.Models
/// <summary>
/// Gets or sets the msal.js cache options.
/// </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>
/// 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.Cache = new MsalCacheOptions
{
CacheLocation = "localStorage",
StoreAuthStateInCookie = true
};
}
public void PostConfigure(string name, RemoteAuthenticationOptions<MsalProviderOptions> options)

View File

@ -24,13 +24,21 @@ namespace Microsoft.Extensions.DependencyInjection
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddMsalAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
{
services.AddRemoteAuthentication<RemoteAuthenticationState, MsalProviderOptions>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>());
return AddMsalAuthentication<RemoteAuthenticationState>(services, configure);
}
if (configure != null)
{
services.Configure(configure);
}
/// <summary>
/// Adds authentication using msal.js to Blazor applications.
/// </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;
}

View File

@ -253,15 +253,21 @@ class OidcAuthorizeService implements AuthorizeService {
export class AuthenticationService {
static _infrastructureKey = 'Microsoft.AspNetCore.Components.WebAssembly.Authentication';
static _initialized = false;
static _initialized : Promise<void>;
static instance: OidcAuthorizeService;
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) {
AuthenticationService._initialized = true;
const userManager = await this.createUserManager(settings);
AuthenticationService.instance = new OidcAuthorizeService(userManager);
this._initialized = (async () => {
const userManager = await this.createUserManager(settings);
AuthenticationService.instance = new OidcAuthorizeService(userManager);
})();
}
await this._initialized;
}
public static getUser() {

View File

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

View File

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

View File

@ -18,11 +18,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
{
private string _message;
private RemoteAuthenticationApplicationPathsOptions _applicationPaths;
private string _action;
/// <summary>
/// Gets or sets the <see cref="RemoteAuthenticationActions"/> action the component needs to handle.
/// </summary>
[Parameter] public string Action { get; set; }
[Parameter] public string Action { get => _action; set => _action = value?.ToLowerInvariant(); }
/// <summary>
/// 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>
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>());
if (configure != null)
{
services.Configure(configure);
}
return services;
return AddRemoteAuthentication<TRemoteAuthenticationState, OidcProviderOptions>(services, configure);
}
/// <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>
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
public static IServiceCollection AddApiAuthorization(this IServiceCollection services)
{
var inferredClientId = Assembly.GetCallingAssembly().GetName().Name;
services.AddRemoteAuthentication<RemoteAuthenticationState, ApiAuthorizationProviderOptions>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>, DefaultApiAuthorizationOptionsConfiguration>(_ =>
new DefaultApiAuthorizationOptionsConfiguration(inferredClientId)));
return services;
return AddApiauthorizationCore<RemoteAuthenticationState>(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>
/// <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="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(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure)
{
services.AddApiAuthorization();
return AddApiauthorizationCore<RemoteAuthenticationState>(services, configure, Assembly.GetCallingAssembly().GetName().Name);
}
if (configure != null)
{
services.Configure(configure);
}
/// <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="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;
}

View File

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

View File

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

View File

@ -3,6 +3,6 @@
@code {
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();
#if (OrganizationalAuth || IndividualAuth)
app.UseAuthentication();
#endif
#if (IndividualLocalAuth)
app.UseIdentityServer();
#endif
#if (OrganizationalAuth || IndividualAuth)
app.UseAuthentication();
#endif
#if (!NoAuth)
app.UseAuthorization();