diff --git a/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs index eb7f275..9b4b9e6 100644 --- a/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs @@ -1,25 +1,31 @@ -using System.Threading.Tasks; -using AutoMapper.Configuration.Annotations; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.eShopWeb.Infrastructure.Identity; +using Microsoft.eShopWeb.Web.Configuration; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account { - [AllowAnonymous] - [IgnoreAntiforgeryToken] + //TODO : replace IMemoryCache by distributed cache if you are in multi-host scenario public class LogoutModel : PageModel { private readonly SignInManager _signInManager; private readonly ILogger _logger; + private readonly IMemoryCache _cache; - public LogoutModel(SignInManager signInManager, ILogger logger) + public LogoutModel(SignInManager signInManager, ILogger logger, IMemoryCache cache) { _signInManager = signInManager; _logger = logger; + _cache = cache; } public void OnGet() @@ -29,6 +35,14 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account public async Task OnPost(string returnUrl = null) { await _signInManager.SignOutAsync(); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name); + var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName]; + _cache.Set($"{userId.Value}:{identityKey}", identityKey, new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod) + }); + _logger.LogInformation("User logged out."); if (returnUrl != null) { diff --git a/src/Web/Configuration/ConfigureCookieSettings.cs b/src/Web/Configuration/ConfigureCookieSettings.cs index 69515b4..61b9791 100644 --- a/src/Web/Configuration/ConfigureCookieSettings.cs +++ b/src/Web/Configuration/ConfigureCookieSettings.cs @@ -7,6 +7,9 @@ namespace Microsoft.eShopWeb.Web.Configuration { public static class ConfigureCookieSettings { + public const int ValidityMinutesPeriod = 60; + public const string IdentifierCookieName = "EshopIdentifier"; + public static IServiceCollection AddCookieSettings(this IServiceCollection services) { services.Configure(options => @@ -18,16 +21,20 @@ namespace Microsoft.eShopWeb.Web.Configuration }); services.ConfigureApplicationCookie(options => { + options.EventsType = typeof(RevokeAuthenticationEvents); options.Cookie.HttpOnly = true; - options.ExpireTimeSpan = TimeSpan.FromHours(1); + options.ExpireTimeSpan = TimeSpan.FromMinutes(ValidityMinutesPeriod); options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; options.Cookie = new CookieBuilder { + Name = IdentifierCookieName, IsEssential = true // required for auth to work without explicit user consent; adjust to suit your privacy policy }; }); + services.AddScoped(); + return services; } } diff --git a/src/Web/Configuration/RevokeAuthenticationEvents.cs b/src/Web/Configuration/RevokeAuthenticationEvents.cs new file mode 100644 index 0000000..6414082 --- /dev/null +++ b/src/Web/Configuration/RevokeAuthenticationEvents.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Web.Configuration +{ + //TODO : replace IMemoryCache with a distributed cache if you are in multi-host scenario + public class RevokeAuthenticationEvents : CookieAuthenticationEvents + { + private readonly IMemoryCache _cache; + private readonly ILogger _logger; + + public RevokeAuthenticationEvents(IMemoryCache cache, ILogger logger) + { + _cache = cache; + _logger = logger; + } + + public override async Task ValidatePrincipal(CookieValidatePrincipalContext context) + { + var userId = context.Principal.Claims.First(c => c.Type == ClaimTypes.Name); + var identityKey = context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName]; + + if (_cache.TryGetValue($"{userId.Value}:{identityKey}", out var revokeKeys)) + { + _logger.LogDebug($"Access has been revoked for: {userId.Value}."); + context.RejectPrincipal(); + await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + } + } + } +}