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>
This commit is contained in:
@@ -1,25 +1,31 @@
|
|||||||
using System.Threading.Tasks;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using AutoMapper.Configuration.Annotations;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||||
|
using Microsoft.eShopWeb.Web.Configuration;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
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
|
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
|
||||||
{
|
{
|
||||||
[AllowAnonymous]
|
//TODO : replace IMemoryCache by distributed cache if you are in multi-host scenario
|
||||||
[IgnoreAntiforgeryToken]
|
|
||||||
public class LogoutModel : PageModel
|
public class LogoutModel : PageModel
|
||||||
{
|
{
|
||||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
private readonly ILogger<LogoutModel> _logger;
|
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;
|
_signInManager = signInManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
@@ -29,6 +35,14 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
|
|||||||
public async Task<IActionResult> OnPost(string returnUrl = null)
|
public async Task<IActionResult> OnPost(string returnUrl = null)
|
||||||
{
|
{
|
||||||
await _signInManager.SignOutAsync();
|
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.");
|
_logger.LogInformation("User logged out.");
|
||||||
if (returnUrl != null)
|
if (returnUrl != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ namespace Microsoft.eShopWeb.Web.Configuration
|
|||||||
{
|
{
|
||||||
public static class ConfigureCookieSettings
|
public static class ConfigureCookieSettings
|
||||||
{
|
{
|
||||||
|
public const int ValidityMinutesPeriod = 60;
|
||||||
|
public const string IdentifierCookieName = "EshopIdentifier";
|
||||||
|
|
||||||
public static IServiceCollection AddCookieSettings(this IServiceCollection services)
|
public static IServiceCollection AddCookieSettings(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.Configure<CookiePolicyOptions>(options =>
|
services.Configure<CookiePolicyOptions>(options =>
|
||||||
@@ -18,16 +21,20 @@ namespace Microsoft.eShopWeb.Web.Configuration
|
|||||||
});
|
});
|
||||||
services.ConfigureApplicationCookie(options =>
|
services.ConfigureApplicationCookie(options =>
|
||||||
{
|
{
|
||||||
|
options.EventsType = typeof(RevokeAuthenticationEvents);
|
||||||
options.Cookie.HttpOnly = true;
|
options.Cookie.HttpOnly = true;
|
||||||
options.ExpireTimeSpan = TimeSpan.FromHours(1);
|
options.ExpireTimeSpan = TimeSpan.FromMinutes(ValidityMinutesPeriod);
|
||||||
options.LoginPath = "/Account/Login";
|
options.LoginPath = "/Account/Login";
|
||||||
options.LogoutPath = "/Account/Logout";
|
options.LogoutPath = "/Account/Logout";
|
||||||
options.Cookie = new CookieBuilder
|
options.Cookie = new CookieBuilder
|
||||||
{
|
{
|
||||||
|
Name = IdentifierCookieName,
|
||||||
IsEssential = true // required for auth to work without explicit user consent; adjust to suit your privacy policy
|
IsEssential = true // required for auth to work without explicit user consent; adjust to suit your privacy policy
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddScoped<RevokeAuthenticationEvents>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/Web/Configuration/RevokeAuthenticationEvents.cs
Normal file
36
src/Web/Configuration/RevokeAuthenticationEvents.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user