Ver código fonte

[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.
Javier Calvarro Nelson 6 anos atrás
pai
commit
b0a95d05e0
14 arquivos alterados com 112 adições e 54 exclusões
  1. 7 11
      src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts
  2. 1 0
      src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json
  3. 6 1
      src/Components/WebAssembly/Authentication.Msal/src/Models/MsalProviderOptions.cs
  4. 0 5
      src/Components/WebAssembly/Authentication.Msal/src/MsalDefaultOptionsConfiguration.cs
  5. 14 6
      src/Components/WebAssembly/Authentication.Msal/src/MsalWebAssemblyServiceCollectionExtensions.cs
  6. 10 4
      src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts
  7. 1 0
      src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/package.json
  8. 5 0
      src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs
  9. 2 1
      src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs
  10. 53 20
      src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs
  11. 1 1
      src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs
  12. 8 1
      src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor
  13. 1 1
      src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/RedirectToLogin.razor
  14. 3 3
      src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs

+ 7 - 11
src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts

@@ -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) {

+ 1 - 0
src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json

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

+ 6 - 1
src/Components/WebAssembly/Authentication.Msal/src/Models/MsalProviderOptions.cs

@@ -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.

+ 0 - 5
src/Components/WebAssembly/Authentication.Msal/src/MsalDefaultOptionsConfiguration.cs

@@ -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)

+ 14 - 6
src/Components/WebAssembly/Authentication.Msal/src/MsalWebAssemblyServiceCollectionExtensions.cs

@@ -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;
         }

+ 10 - 4
src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts

@@ -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() {

+ 1 - 0
src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/package.json

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

+ 5 - 0
src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs

@@ -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>

+ 2 - 1
src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs

@@ -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.

+ 53 - 20
src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs

@@ -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 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;
         }

+ 1 - 1
src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs

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

+ 8 - 1
src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor

@@ -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>

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

@@ -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)}");
     }
 }

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

@@ -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();