Skip to content

[Blazor] Auth improvements #20073

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

Closed
Closed
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 @@ -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 @@ -23,15 +23,23 @@ public static class MsalWebAssemblyServiceCollectionExtensions
/// <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(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
{
return AddMsalAuthentication<RemoteAuthenticationState>(services, 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the configure parameter isn't used. Am I missing something? Why don't we call it any more?

where TRemoteAuthenticationState : RemoteAuthenticationState, new()
{
services.AddRemoteAuthentication<RemoteAuthenticationState, MsalProviderOptions>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>());

if (configure != null)
{
services.Configure(configure);
}

return services;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,26 @@ 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 = new Promise(async (resolve, reject) => {
try {
const userManager = await this.createUserManager(settings);
AuthenticationService.instance = new OidcAuthorizeService(userManager);
resolve();
} catch (e) {
reject(e);
}
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor stylistic point but rather than manually creating a promise and having a try/catch, it could do:

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 @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@if(!context.User.Identity.IsAuthenticated)
@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