Skip to content

Commit b0a95d0

Browse files
authored
[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.
1 parent 3e07040 commit b0a95d0

File tree

14 files changed

+112
-54
lines changed

14 files changed

+112
-54
lines changed

src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,12 @@ class MsalAuthorizeService implements AuthorizeService {
9292
scopes: scopes || this._settings.defaultAccessTokenScopes
9393
};
9494

95-
try {
96-
const response = await this._msalApplication.acquireTokenSilent(tokenScopes);
97-
return {
98-
value: response.accessToken,
99-
grantedScopes: response.scopes,
100-
expires: response.expiresOn
101-
};
102-
} catch (e) {
103-
return undefined;
104-
}
95+
const response = await this._msalApplication.acquireTokenSilent(tokenScopes);
96+
return {
97+
value: response.accessToken,
98+
grantedScopes: response.scopes,
99+
expires: response.expiresOn
100+
};
105101
}
106102

107103
async signIn(state: any) {
@@ -236,7 +232,7 @@ class MsalAuthorizeService implements AuthorizeService {
236232
// msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state.
237233
// The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format
238234
// simple uses <<base64urlIdentifier>>.
239-
const appState = !isLogout? this._msalApplication.getAccountState(state[0]): state[0];
235+
const appState = !isLogout ? this._msalApplication.getAccountState(state[0]) : state[0];
240236
const stateKey = `${AuthenticationService._infrastructureKey}.AuthorizeService.${appState}`;
241237
const stateString = sessionStorage.getItem(stateKey);
242238
if (stateString) {

src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"private": true,
23
"scripts": {
34
"build": "npm run build:release",
45
"build:release": "webpack --mode production --env.production --env.configuration=Release",

src/Components/WebAssembly/Authentication.Msal/src/Models/MsalProviderOptions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ public class MsalProviderOptions
2424
/// <summary>
2525
/// Gets or sets the msal.js cache options.
2626
/// </summary>
27-
public MsalCacheOptions Cache { get; set; }
27+
public MsalCacheOptions Cache { get; set; } = new MsalCacheOptions
28+
{
29+
// This matches the defaults in msal.js
30+
CacheLocation = "sessionStorage",
31+
StoreAuthStateInCookie = false
32+
};
2833

2934
/// <summary>
3035
/// Gets or set the list of default access tokens scopes to provision during the sign-in flow.

src/Components/WebAssembly/Authentication.Msal/src/MsalDefaultOptionsConfiguration.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ public void Configure(RemoteAuthenticationOptions<MsalProviderOptions> options)
4040
}
4141

4242
options.ProviderOptions.Authentication.NavigateToLoginRequestUrl = false;
43-
options.ProviderOptions.Cache = new MsalCacheOptions
44-
{
45-
CacheLocation = "localStorage",
46-
StoreAuthStateInCookie = true
47-
};
4843
}
4944

5045
public void PostConfigure(string name, RemoteAuthenticationOptions<MsalProviderOptions> options)

src/Components/WebAssembly/Authentication.Msal/src/MsalWebAssemblyServiceCollectionExtensions.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,21 @@ public static class MsalWebAssemblyServiceCollectionExtensions
2424
/// <returns>The <see cref="IServiceCollection"/>.</returns>
2525
public static IServiceCollection AddMsalAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
2626
{
27-
services.AddRemoteAuthentication<RemoteAuthenticationState, MsalProviderOptions>();
28-
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>());
27+
return AddMsalAuthentication<RemoteAuthenticationState>(services, configure);
28+
}
2929

30-
if (configure != null)
31-
{
32-
services.Configure(configure);
33-
}
30+
/// <summary>
31+
/// Adds authentication using msal.js to Blazor applications.
32+
/// </summary>
33+
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
34+
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
35+
/// <param name="configure">The <see cref="Action{RemoteAuthenticationOptions{MsalProviderOptions}}"/> to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param>
36+
/// <returns>The <see cref="IServiceCollection"/>.</returns>
37+
public static IServiceCollection AddMsalAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
38+
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
39+
{
40+
services.AddRemoteAuthentication<RemoteAuthenticationState, MsalProviderOptions>(configure);
41+
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>());
3442

3543
return services;
3644
}

src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,15 +253,21 @@ class OidcAuthorizeService implements AuthorizeService {
253253
export class AuthenticationService {
254254

255255
static _infrastructureKey = 'Microsoft.AspNetCore.Components.WebAssembly.Authentication';
256-
static _initialized = false;
256+
static _initialized : Promise<void>;
257257
static instance: OidcAuthorizeService;
258258

259259
public static async init(settings: UserManagerSettings & AuthorizeServiceSettings) {
260+
// Multiple initializations can start concurrently and we want to avoid that.
261+
// In order to do so, we create an initialization promise and the first call to init
262+
// tries to initialize the app and sets up a promise other calls can await on.
260263
if (!AuthenticationService._initialized) {
261-
AuthenticationService._initialized = true;
262-
const userManager = await this.createUserManager(settings);
263-
AuthenticationService.instance = new OidcAuthorizeService(userManager);
264+
this._initialized = (async () => {
265+
const userManager = await this.createUserManager(settings);
266+
AuthenticationService.instance = new OidcAuthorizeService(userManager);
267+
})();
264268
}
269+
270+
await this._initialized;
265271
}
266272

267273
public static getUser() {

src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"private": true,
23
"scripts": {
34
"build": "npm run build:release",
45
"build:release": "webpack --mode production --env.production --env.configuration=Release",

src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ public class OidcProviderOptions
1616
/// </summary>
1717
public string Authority { get; set; }
1818

19+
/// <summary>
20+
/// Gets or sets the metadata url of the oidc provider.
21+
/// </summary>
22+
public string MetadataUrl { get; set; }
23+
1924
/// <summary>
2025
/// Gets or sets the client of the application.
2126
/// </summary>

src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ public class RemoteAuthenticatorViewCore<TAuthenticationState> : ComponentBase w
1818
{
1919
private string _message;
2020
private RemoteAuthenticationApplicationPathsOptions _applicationPaths;
21+
private string _action;
2122

2223
/// <summary>
2324
/// Gets or sets the <see cref="RemoteAuthenticationActions"/> action the component needs to handle.
2425
/// </summary>
25-
[Parameter] public string Action { get; set; }
26+
[Parameter] public string Action { get => _action; set => _action = value?.ToLowerInvariant(); }
2627

2728
/// <summary>
2829
/// Gets or sets the <typeparamref name="TAuthenticationState"/> instance to be preserved during the authentication operation.

src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -78,50 +78,83 @@ public static IServiceCollection AddRemoteAuthentication<TRemoteAuthenticationSt
7878
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
7979
public static IServiceCollection AddOidcAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure)
8080
{
81-
AddRemoteAuthentication<RemoteAuthenticationState, OidcProviderOptions>(services, configure);
81+
return AddOidcAuthentication<RemoteAuthenticationState>(services, configure);
82+
}
8283

84+
/// <summary>
85+
/// Adds support for authentication for SPA applications using <see cref="OidcProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
86+
/// </summary>
87+
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
88+
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
89+
/// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{TProviderOptions}"/>.</param>
90+
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
91+
public static IServiceCollection AddOidcAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure)
92+
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
93+
{
8394
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<OidcProviderOptions>>, DefaultOidcOptionsConfiguration>());
8495

85-
if (configure != null)
86-
{
87-
services.Configure(configure);
88-
}
89-
90-
return services;
96+
return AddRemoteAuthentication<TRemoteAuthenticationState, OidcProviderOptions>(services, configure);
9197
}
9298

9399
/// <summary>
94100
/// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
95101
/// </summary>
102+
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
96103
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
97104
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
98105
public static IServiceCollection AddApiAuthorization(this IServiceCollection services)
99106
{
100-
var inferredClientId = Assembly.GetCallingAssembly().GetName().Name;
101-
102-
services.AddRemoteAuthentication<RemoteAuthenticationState, ApiAuthorizationProviderOptions>();
103-
104-
services.TryAddEnumerable(
105-
ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>, DefaultApiAuthorizationOptionsConfiguration>(_ =>
106-
new DefaultApiAuthorizationOptionsConfiguration(inferredClientId)));
107+
return AddApiauthorizationCore<RemoteAuthenticationState>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name);
108+
}
107109

108-
return services;
110+
/// <summary>
111+
/// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
112+
/// </summary>
113+
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
114+
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
115+
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
116+
public static IServiceCollection AddApiAuthorization<TRemoteAuthenticationState>(this IServiceCollection services)
117+
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
118+
{
119+
return AddApiauthorizationCore<TRemoteAuthenticationState>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name);
109120
}
110121

111122
/// <summary>
112123
/// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
113124
/// </summary>
125+
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
114126
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
115127
/// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param>
116128
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
117129
public static IServiceCollection AddApiAuthorization(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure)
118130
{
119-
services.AddApiAuthorization();
131+
return AddApiauthorizationCore<RemoteAuthenticationState>(services, configure, Assembly.GetCallingAssembly().GetName().Name);
132+
}
120133

121-
if (configure != null)
122-
{
123-
services.Configure(configure);
124-
}
134+
/// <summary>
135+
/// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>.
136+
/// </summary>
137+
/// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam>
138+
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
139+
/// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param>
140+
/// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns>
141+
public static IServiceCollection AddApiAuthorization<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure)
142+
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
143+
{
144+
return AddApiauthorizationCore<TRemoteAuthenticationState>(services, configure, Assembly.GetCallingAssembly().GetName().Name);
145+
}
146+
147+
private static IServiceCollection AddApiauthorizationCore<TRemoteAuthenticationState>(
148+
IServiceCollection services,
149+
Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure,
150+
string inferredClientId)
151+
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
152+
{
153+
services.TryAddEnumerable(
154+
ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>, DefaultApiAuthorizationOptionsConfiguration>(_ =>
155+
new DefaultApiAuthorizationOptionsConfiguration(inferredClientId)));
156+
157+
services.AddRemoteAuthentication<TRemoteAuthenticationState, ApiAuthorizationProviderOptions>(configure);
125158

126159
return services;
127160
}

src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
5252

5353
app.UseRouting();
5454

55-
app.UseAuthentication();
5655
app.UseIdentityServer();
56+
app.UseAuthentication();
5757
app.UseAuthorization();
5858

5959
app.UseEndpoints(endpoints =>

src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@
1515
<Found Context="routeData">
1616
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
1717
<NotAuthorized>
18-
<RedirectToLogin />
18+
@if (!context.User.Identity.IsAuthenticated)
19+
{
20+
<RedirectToLogin />
21+
}
22+
else
23+
{
24+
<p>You are not authorized to access this resource.</p>
25+
}
1926
</NotAuthorized>
2027
</AuthorizeRouteView>
2128
</Found>

src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/RedirectToLogin.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
@code {
44
protected override void OnInitialized()
55
{
6-
Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}");
6+
Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
77
}
88
}

src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
107107

108108
app.UseRouting();
109109

110-
#if (OrganizationalAuth || IndividualAuth)
111-
app.UseAuthentication();
112-
#endif
113110
#if (IndividualLocalAuth)
114111
app.UseIdentityServer();
115112
#endif
113+
#if (OrganizationalAuth || IndividualAuth)
114+
app.UseAuthentication();
115+
#endif
116116
#if (!NoAuth)
117117
app.UseAuthorization();
118118

0 commit comments

Comments
 (0)