Browse Source

add cached on logout with revoke cookie identity key (#605)

* add cached on logout with revoke cookie identity key

* properly signout as recommended : https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0#react-to-back-end-changes

* add remark regarding multi-host scenario

* Update src/Web/Configuration/RevokeAuthenticationEvents.cs

Co-authored-by: Steve Smith <steve@kentsmiths.com>
main
Cédric Michel 4 years ago
committed by GitHub
parent
commit
5d34222f28
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs
  2. 9
      src/Web/Configuration/ConfigureCookieSettings.cs
  3. 36
      src/Web/Configuration/RevokeAuthenticationEvents.cs

26
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<ApplicationUser> _signInManager;
private readonly ILogger<LogoutModel> _logger;
private readonly IMemoryCache _cache;
public LogoutModel(SignInManager<ApplicationUser> signInManager, ILogger<LogoutModel> logger)
public LogoutModel(SignInManager<ApplicationUser> signInManager, ILogger<LogoutModel> 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<IActionResult> 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)
{

9
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<CookiePolicyOptions>(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<RevokeAuthenticationEvents>();
return services;
}
}

36
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<RevokeAuthenticationEvents> 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);
}
}
}
}
Loading…
Cancel
Save