Skip to content

[Blazor] Auth fixes #20191

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

Merged
merged 4 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"private": true,
"scripts": {
"build": "npm run build:release",
"build:release": "webpack --mode production --env.production --env.configuration=Release",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ public class MsalProviderOptions
/// <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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ public void Configure(RemoteAuthenticationOptions<MsalProviderOptions> options)
}

options.ProviderOptions.Authentication.NavigateToLoginRequestUrl = false;
options.ProviderOptions.Cache = new MsalCacheOptions
{
CacheLocation = "localStorage",
StoreAuthStateInCookie = true
};
}

public void PostConfigure(string name, RemoteAuthenticationOptions<MsalProviderOptions> options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@ public static class MsalWebAssemblyServiceCollectionExtensions
/// <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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"private": true,
"scripts": {
"build": "npm run build:release",
"build:release": "webpack --mode production --env.production --env.configuration=Release",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public class OidcProviderOptions
/// </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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ public class RemoteAuthenticatorViewCore<TAuthenticationState> : ComponentBase w
{
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,50 +78,83 @@ public static IServiceCollection AddRemoteAuthentication<TRemoteAuthenticationSt
/// <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 AddApiauthorizationCore<RemoteAuthenticationState>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name);
}

return services;
/// <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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.UseRouting();

#if (OrganizationalAuth || IndividualAuth)
app.UseAuthentication();
#endif
#if (IndividualLocalAuth)
app.UseIdentityServer();
#endif
#if (OrganizationalAuth || IndividualAuth)
app.UseAuthentication();
#endif
#if (!NoAuth)
app.UseAuthorization();

Expand Down