diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs index 0dd2c33cb84..2aead07ce77 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs @@ -1,86 +1,16 @@ using System; -using System.Threading.Tasks; -using Duende.IdentityModel.Client; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Volo.Abp.Threading; namespace Microsoft.AspNetCore.Authentication.Cookies; public static class CookieAuthenticationOptionsExtensions { /// - /// Introspect access token on validating the principal. + /// Check the access_token is expired or inactive. /// - /// - /// - /// + [Obsolete("Use CheckTokenExpiration method instead.")] public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc") { - options.Events.OnValidatePrincipal = async principalContext => - { - if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) - { - return; - } - - var logger = principalContext.HttpContext.RequestServices.GetRequiredService>(); - - var accessToken = principalContext.Properties.GetTokenValue("access_token"); - if (!accessToken.IsNullOrWhiteSpace()) - { - var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme); - var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest - { - Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority!.EnsureEndsWith('/') + "connect/introspect", - ClientId = openIdConnectOptions.ClientId!, - ClientSecret = openIdConnectOptions.ClientSecret, - Token = accessToken - }); - - if (response.IsError) - { - logger.LogError(response.Error); - await SignOutAsync(principalContext); - return; - } - - if (!response.IsActive) - { - logger.LogError("The access_token is not active."); - await SignOutAsync(principalContext); - return; - } - - logger.LogInformation("The access_token is active."); - } - else - { - logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true."); - await SignOutAsync(principalContext); - } - }; - - return options; - } - - private async static Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) - { - var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); - if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null) - { - var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService(); - openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(cancellationTokenProvider.Token); - } - - return openIdConnectOptions; - } - - private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext) - { - principalContext.RejectPrincipal(); - await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); + return options.CheckTokenExpiration(oidcAuthenticationScheme, null, TimeSpan.FromMinutes(1)); } } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs index c37e75ef324..7d6955f08bb 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs @@ -14,41 +14,56 @@ namespace Microsoft.Extensions.DependencyInjection; public static class CookieAuthenticationOptionsExtensions { /// - /// Check the access_token is expired or inactive. + /// Check if the access_token is expired or inactive. /// public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc", TimeSpan? advance = null, TimeSpan? validationInterval = null) { advance ??= TimeSpan.FromMinutes(3); validationInterval ??= TimeSpan.FromMinutes(1); + var previousHandler = options.Events.OnValidatePrincipal; options.Events.OnValidatePrincipal = async principalContext => { if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) { + await InvokePreviousHandlerAsync(principalContext, previousHandler); return; } var logger = principalContext.HttpContext.RequestServices.GetRequiredService>(); var tokenExpiresAt = principalContext.Properties.GetString(".Token.expires_at"); - if (!tokenExpiresAt.IsNullOrWhiteSpace() && DateTimeOffset.TryParseExact(tokenExpiresAt, "o", null, DateTimeStyles.RoundtripKind, out var expiresAt) && - expiresAt < DateTimeOffset.UtcNow.Subtract(advance.Value)) + if (!tokenExpiresAt.IsNullOrWhiteSpace() && DateTimeOffset.TryParseExact(tokenExpiresAt, "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var expiresAt) && + expiresAt <= DateTimeOffset.UtcNow.Add(advance.Value)) { - logger.LogInformation("The access_token is expired."); - await SignOutAsync(principalContext); + logger.LogInformation("The access_token expires within {AdvanceSeconds}s; signing out.", advance.Value.TotalSeconds); + await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler); return; } if (principalContext.Properties.IssuedUtc != null && DateTimeOffset.UtcNow.Subtract(principalContext.Properties.IssuedUtc.Value) > validationInterval) { - logger.LogInformation($"Check the access_token is active every {validationInterval.Value.TotalSeconds} seconds."); + logger.LogInformation("Checking access_token activity every {Seconds} seconds.", validationInterval.Value.TotalSeconds); var accessToken = principalContext.Properties.GetTokenValue("access_token"); if (!accessToken.IsNullOrWhiteSpace()) { var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme); + var introspectionEndpoint = openIdConnectOptions.Configuration?.IntrospectionEndpoint; + if (introspectionEndpoint.IsNullOrWhiteSpace() && !openIdConnectOptions.Authority.IsNullOrWhiteSpace()) + { + introspectionEndpoint = openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect"; + } + + if (introspectionEndpoint.IsNullOrWhiteSpace()) + { + logger.LogWarning("No introspection endpoint configured. Skipping token activity check."); + await InvokePreviousHandlerAsync(principalContext, previousHandler); + return; + } + var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest { - Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority!.EnsureEndsWith('/') + "connect/introspect", + Address = introspectionEndpoint, ClientId = openIdConnectOptions.ClientId!, ClientSecret = openIdConnectOptions.ClientSecret, Token = accessToken @@ -56,15 +71,15 @@ public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthen if (response.IsError) { - logger.LogError(response.Error); - await SignOutAsync(principalContext); + logger.LogError("Token introspection error: {Error}", response.Error); + await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler); return; } if (!response.IsActive) { logger.LogError("The access_token is not active."); - await SignOutAsync(principalContext); + await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler); return; } @@ -73,16 +88,18 @@ public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthen } else { - logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true."); + logger.LogError("The access_token is not found in the cookie properties. Ensure SaveTokens of OpenIdConnectOptions is true."); await SignOutAsync(principalContext); } } + + await InvokePreviousHandlerAsync(principalContext, previousHandler); }; return options; } - private async static Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) + private static async Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) { var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService(); @@ -94,9 +111,20 @@ private async static Task GetOpenIdConnectOptions(CookieVa return openIdConnectOptions; } - private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext) + private static async Task SignOutAsync(CookieValidatePrincipalContext principalContext) { principalContext.RejectPrincipal(); await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); } + + private static Task InvokePreviousHandlerAsync(CookieValidatePrincipalContext principalContext, Func? previousHandler) + { + return previousHandler != null ? previousHandler(principalContext) : Task.CompletedTask; + } + + private static async Task SignOutAndInvokePreviousHandlerAsync(CookieValidatePrincipalContext principalContext, Func? previousHandler) + { + await SignOutAsync(principalContext); + await InvokePreviousHandlerAsync(principalContext, previousHandler); + } }