// 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; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Identity { /// /// Provides default implementation of validation functions for security stamps. /// /// The type encapsulating a user. public class SecurityStampValidator : ISecurityStampValidator where TUser : class { /// /// Creates a new instance of . /// /// Used to access the . /// The . /// The system clock. public SecurityStampValidator(IOptions options, SignInManager signInManager, ISystemClock clock) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (signInManager == null) { throw new ArgumentNullException(nameof(signInManager)); } SignInManager = signInManager; Options = options.Value; Clock = clock; } /// /// The SignInManager. /// public SignInManager SignInManager { get; } /// /// The . /// public SecurityStampValidatorOptions Options { get; } /// /// The . /// public ISystemClock Clock { get; } /// /// Called when the security stamp has been verified. /// /// The user who has been verified. /// The . /// A task. protected virtual async Task SecurityStampVerified(TUser user, CookieValidatePrincipalContext context) { var newPrincipal = await SignInManager.CreateUserPrincipalAsync(user); if (Options.OnRefreshingPrincipal != null) { var replaceContext = new SecurityStampRefreshingPrincipalContext { CurrentPrincipal = context.Principal, NewPrincipal = newPrincipal }; // Note: a null principal is allowed and results in a failed authentication. await Options.OnRefreshingPrincipal(replaceContext); newPrincipal = replaceContext.NewPrincipal; } // REVIEW: note we lost login authentication method context.ReplacePrincipal(newPrincipal); context.ShouldRenew = true; } /// /// Verifies the principal's security stamp, returns the matching user if successful /// /// The principal to verify. /// The verified user or null if verification fails. protected virtual Task VerifySecurityStamp(ClaimsPrincipal principal) => SignInManager.ValidateSecurityStampAsync(principal); /// /// Validates a security stamp of an identity as an asynchronous operation, and rebuilds the identity if the validation succeeds, otherwise rejects /// the identity. /// /// The context containing the /// and to validate. /// The that represents the asynchronous validation operation. public virtual async Task ValidateAsync(CookieValidatePrincipalContext context) { var currentUtc = DateTimeOffset.UtcNow; if (context.Options != null && Clock != null) { currentUtc = Clock.UtcNow; } var issuedUtc = context.Properties.IssuedUtc; // Only validate if enough time has elapsed var validate = (issuedUtc == null); if (issuedUtc != null) { var timeElapsed = currentUtc.Subtract(issuedUtc.Value); validate = timeElapsed > Options.ValidationInterval; } if (validate) { var user = await VerifySecurityStamp(context.Principal); if (user != null) { await SecurityStampVerified(user, context); } else { context.RejectPrincipal(); await SignInManager.SignOutAsync(); } } } } /// /// Static helper class used to configure a CookieAuthenticationNotifications to validate a cookie against a user's security /// stamp. /// public static class SecurityStampValidator { /// /// Validates a principal against a user's stored security stamp. /// /// The context containing the /// and to validate. /// The that represents the asynchronous validation operation. public static Task ValidatePrincipalAsync(CookieValidatePrincipalContext context) => ValidateAsync(context); /// /// Used to validate the and /// cookies against the user's /// stored security stamp. /// /// The context containing the /// and to validate. /// public static Task ValidateAsync(CookieValidatePrincipalContext context) where TValidator : ISecurityStampValidator { if (context.HttpContext.RequestServices == null) { throw new InvalidOperationException("RequestServices is null."); } var validator = context.HttpContext.RequestServices.GetRequiredService(); return validator.ValidateAsync(context); } } }