diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs index 901b476b3..0431f90f8 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -14,7 +14,10 @@ namespace Microsoft.AspNetCore.Authentication.Cookies { - public class CookieAuthenticationHandler : AuthenticationHandler + public class CookieAuthenticationHandler : + AuthenticationHandler, + IAuthenticationSignInHandler, + IAuthenticationSignOutHandler { private const string HeaderValueNoCache = "no-cache"; private const string HeaderValueMinusOne = "-1"; @@ -249,7 +252,19 @@ protected virtual async Task FinishResponseAsync() } } - protected override async Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) + public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + properties = properties ?? new AuthenticationProperties(); + await HandleSignInAsync(user, properties); + Logger.SignedIn(Scheme.Name); + } + + protected virtual async Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) { _signInCalled = true; @@ -327,7 +342,14 @@ protected override async Task HandleSignInAsync(ClaimsPrincipal user, Authentica await ApplyHeaders(shouldRedirect, signedInContext.Properties); } - protected override async Task HandleSignOutAsync(AuthenticationProperties properties) + public async Task SignOutAsync(AuthenticationProperties properties) + { + properties = properties ?? new AuthenticationProperties(); + await HandleSignOutAsync(properties); + Logger.SignedOut(Scheme.Name); + } + + protected virtual async Task HandleSignOutAsync(AuthenticationProperties properties) { _signOutCalled = true; diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/LoggingExtensions.cs new file mode 100644 index 000000000..d12735443 --- /dev/null +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/LoggingExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Extensions.Logging +{ + internal static class LoggingExtensions + { + private static Action _authSchemeSignedIn; + private static Action _authSchemeSignedOut; + + static LoggingExtensions() + { + _authSchemeSignedIn = LoggerMessage.Define( + eventId: 10, + logLevel: LogLevel.Information, + formatString: "AuthenticationScheme: {AuthenticationScheme} signed in."); + _authSchemeSignedOut = LoggerMessage.Define( + eventId: 11, + logLevel: LogLevel.Information, + formatString: "AuthenticationScheme: {AuthenticationScheme} signed out."); + } + + public static void SignedIn(this ILogger logger, string authenticationScheme) + { + _authSchemeSignedIn(logger, authenticationScheme, null); + } + + public static void SignedOut(this ILogger logger, string authenticationScheme) + { + _authSchemeSignedOut(logger, authenticationScheme, null); + } + } +} diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs index 28f210d29..75b456f84 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; @@ -13,7 +12,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; @@ -328,15 +326,5 @@ private static string CreateErrorDescription(Exception authFailure) return string.Join("; ", messages); } - - protected override Task HandleSignOutAsync(AuthenticationProperties properties) - { - throw new NotSupportedException(); - } - - protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - throw new NotSupportedException(); - } } } diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs index aa6d0cbaa..29699f996 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs @@ -55,6 +55,7 @@ internal static class LoggingExtensions private static Action _remoteSignOut; private static Action _remoteSignOutSessionIdMissing; private static Action _remoteSignOutSessionIdInvalid; + private static Action _signOut; static LoggingExtensions() { @@ -253,6 +254,10 @@ static LoggingExtensions() logLevel: LogLevel.Error, formatString: "The remote signout request was ignored because the 'sid' parameter didn't match " + "the expected value, which may indicate an unsolicited logout."); + _signOut = LoggerMessage.Define( + eventId: 49, + logLevel: LogLevel.Information, + formatString: "AuthenticationScheme: {AuthenticationScheme} signed out."); } public static void UpdatingConfiguration(this ILogger logger) @@ -494,5 +499,10 @@ public static void RemoteSignOutSessionIdInvalid(this ILogger logger) { _remoteSignOutSessionIdInvalid(logger, null); } + + public static void SignedOut(this ILogger logger, string authenticationScheme) + { + _signOut(logger, authenticationScheme, null); + } } } diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index 12bfa124e..1de036148 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware. /// - public class OpenIdConnectHandler : RemoteAuthenticationHandler + public class OpenIdConnectHandler : RemoteAuthenticationHandler, IAuthenticationSignOutHandler { private const string NonceProperty = "N"; private const string UriSchemeDelimiter = "://"; @@ -160,11 +160,18 @@ protected virtual async Task HandleRemoteSignOutAsync() return true; } + public async Task SignOutAsync(AuthenticationProperties properties) + { + properties = properties ?? new AuthenticationProperties(); + await HandleSignOutAsync(properties); + Logger.SignedOut(Scheme.Name); + } + /// /// Redirect user to the identity provider for sign out /// /// A task executing the sign out procedure - protected override async Task HandleSignOutAsync(AuthenticationProperties properties) + protected virtual async Task HandleSignOutAsync(AuthenticationProperties properties) { Logger.EnteringOpenIdAuthenticationHandlerHandleSignOutAsync(GetType().FullName); diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs index 788df7e19..aeb70cb0d 100644 --- a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs @@ -178,35 +178,6 @@ protected async Task HandleAuthenticateOnceSafeAsync() protected abstract Task HandleAuthenticateAsync(); - public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - properties = properties ?? new AuthenticationProperties(); - await HandleSignInAsync(user, properties); - Logger.AuthenticationSchemeSignedIn(Scheme.Name); - } - - protected virtual Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - return Task.CompletedTask; - } - - public async Task SignOutAsync(AuthenticationProperties properties) - { - properties = properties ?? new AuthenticationProperties(); - await HandleSignOutAsync(properties); - Logger.AuthenticationSchemeSignedOut(Scheme.Name); - } - - protected virtual Task HandleSignOutAsync(AuthenticationProperties properties) - { - return Task.CompletedTask; - } - /// /// Override this method to handle Forbid. /// diff --git a/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs index 49fc8db05..86747e23e 100644 --- a/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs @@ -10,8 +10,6 @@ internal static class LoggingExtensions private static Action _authSchemeAuthenticated; private static Action _authSchemeNotAuthenticated; private static Action _authSchemeNotAuthenticatedWithFailure; - private static Action _authSchemeSignedIn; - private static Action _authSchemeSignedOut; private static Action _authSchemeChallenged; private static Action _authSchemeForbidden; private static Action _userAuthorizationFailed; @@ -62,14 +60,6 @@ static LoggingExtensions() eventId: 9, logLevel: LogLevel.Debug, formatString: "AuthenticationScheme: {AuthenticationScheme} was not authenticated."); - _authSchemeSignedIn = LoggerMessage.Define( - eventId: 10, - logLevel: LogLevel.Information, - formatString: "AuthenticationScheme: {AuthenticationScheme} signed in."); - _authSchemeSignedOut = LoggerMessage.Define( - eventId: 11, - logLevel: LogLevel.Information, - formatString: "AuthenticationScheme: {AuthenticationScheme} signed out."); _authSchemeChallenged = LoggerMessage.Define( eventId: 12, logLevel: LogLevel.Information, @@ -107,16 +97,6 @@ public static void AuthenticationSchemeNotAuthenticatedWithFailure(this ILogger _authSchemeNotAuthenticatedWithFailure(logger, authenticationScheme, failureMessage, null); } - public static void AuthenticationSchemeSignedIn(this ILogger logger, string authenticationScheme) - { - _authSchemeSignedIn(logger, authenticationScheme, null); - } - - public static void AuthenticationSchemeSignedOut(this ILogger logger, string authenticationScheme) - { - _authSchemeSignedOut(logger, authenticationScheme, null); - } - public static void AuthenticationSchemeChallenged(this ILogger logger, string authenticationScheme) { _authSchemeChallenged(logger, authenticationScheme, null); diff --git a/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj b/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj index ce85125d2..6b4c8ffb3 100644 --- a/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj +++ b/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs index 75c87c834..d0f990216 100644 --- a/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Security.Claims; using System.Security.Cryptography; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -180,16 +179,6 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.Fail("Remote authentication does not directly support AuthenticateAsync"); } - protected override Task HandleSignOutAsync(AuthenticationProperties properties) - { - throw new NotSupportedException(); - } - - protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) - { - throw new NotSupportedException(); - } - protected override Task HandleForbiddenAsync(AuthenticationProperties properties) { return Context.ForbidAsync(SignInScheme); diff --git a/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs index 501fdf603..934fbf0c2 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs @@ -1024,15 +1024,15 @@ private static TestServer CreateServer(Action configureOptions, F } else if (req.Path == new PathString("/signIn")) { - await Assert.ThrowsAsync(() => context.SignInAsync("Google", new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync("Google", new ClaimsPrincipal())); } else if (req.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync("Google")); + await Assert.ThrowsAsync(() => context.SignOutAsync("Google")); } else if (req.Path == new PathString("/forbid")) { - await Assert.ThrowsAsync(() => context.ForbidAsync("Google")); + await Assert.ThrowsAsync(() => context.ForbidAsync("Google")); } else if (testpath != null) { diff --git a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs index 2a478333c..70844d5db 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs @@ -738,11 +738,11 @@ private static TestServer CreateServer(Action options = null, } else if (context.Request.Path == new PathString("/signIn")) { - await Assert.ThrowsAsync(() => context.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal())); } else if (context.Request.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync(JwtBearerDefaults.AuthenticationScheme)); + await Assert.ThrowsAsync(() => context.SignOutAsync(JwtBearerDefaults.AuthenticationScheme)); } else { diff --git a/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj b/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj index 1529dc803..2e5eac0fa 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj +++ b/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj @@ -17,6 +17,10 @@ + + + + diff --git a/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs index bf15c91b0..e1986716f 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs @@ -205,15 +205,15 @@ private static TestServer CreateServer(Action configure } else if (req.Path == new PathString("/signIn")) { - await Assert.ThrowsAsync(() => context.SignInAsync("Microsoft", new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync("Microsoft", new ClaimsPrincipal())); } else if (req.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync("Microsoft")); + await Assert.ThrowsAsync(() => context.SignOutAsync("Microsoft")); } else if (req.Path == new PathString("/forbid")) { - await Assert.ThrowsAsync(() => context.ForbidAsync("Microsoft")); + await Assert.ThrowsAsync(() => context.ForbidAsync("Microsoft")); } else { diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs index 6cfda3b85..7f9d42b1c 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -11,10 +10,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.DataProtection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Xunit; diff --git a/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs index 8fcc0780d..d5bd5820f 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs @@ -177,15 +177,15 @@ private static TestServer CreateServer(Action options, Func(() => context.SignInAsync("Twitter", new ClaimsPrincipal())); + await Assert.ThrowsAsync(() => context.SignInAsync("Twitter", new ClaimsPrincipal())); } else if (req.Path == new PathString("/signOut")) { - await Assert.ThrowsAsync(() => context.SignOutAsync("Twitter")); + await Assert.ThrowsAsync(() => context.SignOutAsync("Twitter")); } else if (req.Path == new PathString("/forbid")) { - await Assert.ThrowsAsync(() => context.ForbidAsync("Twitter")); + await Assert.ThrowsAsync(() => context.ForbidAsync("Twitter")); } else if (handler == null || !handler(context)) {