// 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.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Identity
{
///
/// Provides the APIs for user sign in.
///
/// The type encapsulating a user.
public class SignInManager where TUser : class
{
private const string LoginProviderKey = "LoginProvider";
private const string XsrfKey = "XsrfId";
///
/// Creates a new instance of .
///
/// An instance of used to retrieve users from and persist users.
/// The accessor used to access the .
/// The factory to use to create claims principals for a user.
/// The accessor used to access the .
/// The logger used to log messages, warnings and errors.
public SignInManager(UserManager userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory claimsFactory,
IOptions optionsAccessor,
ILogger> logger)
{
if (userManager == null)
{
throw new ArgumentNullException(nameof(userManager));
}
if (contextAccessor == null)
{
throw new ArgumentNullException(nameof(contextAccessor));
}
if (claimsFactory == null)
{
throw new ArgumentNullException(nameof(claimsFactory));
}
UserManager = userManager;
_contextAccessor = contextAccessor;
ClaimsFactory = claimsFactory;
Options = optionsAccessor?.Value ?? new IdentityOptions();
Logger = logger;
}
private readonly IHttpContextAccessor _contextAccessor;
private HttpContext _context;
///
/// Gets the used to log messages from the manager.
///
///
/// The used to log messages from the manager.
///
protected internal virtual ILogger Logger { get; set; }
///
/// The used.
///
protected internal UserManager UserManager { get; set; }
internal IUserClaimsPrincipalFactory ClaimsFactory { get; set; }
internal IdentityOptions Options { get; set; }
internal HttpContext Context {
get
{
var context = _context ?? _contextAccessor?.HttpContext;
if (context == null)
{
throw new InvalidOperationException("HttpContext must not be null.");
}
return context;
}
set
{
_context = value;
}
}
///
/// Creates a for the specified , as an asynchronous operation.
///
/// The user to create a for.
/// The task object representing the asynchronous operation, containing the ClaimsPrincipal for the specified user.
public virtual async Task CreateUserPrincipalAsync(TUser user) => await ClaimsFactory.CreateAsync(user);
///
/// Returns true if the principal has an identity with the application cookie identity
///
/// The instance.
/// True if the user is logged in with identity.
public virtual bool IsSignedIn(ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal?.Identities != null &&
principal.Identities.Any(i => i.AuthenticationType == Options.Cookies.ApplicationCookieAuthenticationScheme);
}
///
/// Returns a flag indicating whether the specified user can sign in.
///
/// The user whose sign-in status should be returned.
///
/// The task object representing the asynchronous operation, containing a flag that is true
/// if the specified user can sign-in, otherwise false.
///
public virtual async Task CanSignInAsync(TUser user)
{
if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user)))
{
Logger.LogWarning(0, "User {userId} cannot sign in without a confirmed email.", await UserManager.GetUserIdAsync(user));
return false;
}
if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user)))
{
Logger.LogWarning(1, "User {userId} cannot sign in without a confirmed phone number.", await UserManager.GetUserIdAsync(user));
return false;
}
return true;
}
///
/// Regenerates the user's application cookie, whilst preserving the existing
/// AuthenticationProperties like rememberMe, as an asynchronous operation.
///
/// The user whose sign-in cookie should be refreshed.
/// The task object representing the asynchronous operation.
public virtual async Task RefreshSignInAsync(TUser user)
{
var auth = new AuthenticateContext(Options.Cookies.ApplicationCookieAuthenticationScheme);
await Context.Authentication.AuthenticateAsync(auth);
var authenticationMethod = auth.Principal?.FindFirstValue(ClaimTypes.AuthenticationMethod);
await SignInAsync(user, new AuthenticationProperties(auth.Properties), authenticationMethod);
}
///
/// Signs in the specified .
///
/// The user to sign-in.
/// Flag indicating whether the sign-in cookie should persist after the browser is closed.
/// Name of the method used to authenticate the user.
/// The task object representing the asynchronous operation.
public virtual Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null)
{
return SignInAsync(user, new AuthenticationProperties { IsPersistent = isPersistent }, authenticationMethod);
}
///
/// Signs in the specified .
///
/// The user to sign-in.
/// Properties applied to the login and authentication cookie.
/// Name of the method used to authenticate the user.
/// The task object representing the asynchronous operation.
public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null)
{
var userPrincipal = await CreateUserPrincipalAsync(user);
// Review: should we guard against CreateUserPrincipal returning null?
if (authenticationMethod != null)
{
userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
}
await Context.Authentication.SignInAsync(Options.Cookies.ApplicationCookieAuthenticationScheme,
userPrincipal,
authenticationProperties ?? new AuthenticationProperties());
}
///
/// Signs the current user out of the application.
///
public virtual async Task SignOutAsync()
{
await Context.Authentication.SignOutAsync(Options.Cookies.ApplicationCookieAuthenticationScheme);
await Context.Authentication.SignOutAsync(Options.Cookies.ExternalCookieAuthenticationScheme);
await Context.Authentication.SignOutAsync(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme);
}
///
/// Validates the security stamp for the specified against
/// the persisted stamp for the current user, as an asynchronous operation.
///
/// The principal whose stamp should be validated.
/// The task object representing the asynchronous operation. The task will contain the
/// if the stamp matches the persisted value, otherwise it will return false.
public virtual async Task ValidateSecurityStampAsync(ClaimsPrincipal principal)
{
if (principal == null)
{
return null;
}
var user = await UserManager.GetUserAsync(principal);
if (user != null && UserManager.SupportsUserSecurityStamp)
{
var securityStamp =
principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType);
if (securityStamp == await UserManager.GetSecurityStampAsync(user))
{
return user;
}
}
return null;
}
///
/// Attempts to sign in the specified and combination
/// as an asynchronous operation.
///
/// The user to sign in.
/// The password to attempt to sign in with.
/// Flag indicating whether the sign-in cookie should persist after the browser is closed.
/// Flag indicating if the user account should be locked if the sign in fails.
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
public virtual async Task PasswordSignInAsync(TUser user, string password,
bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
///
/// Attempts to sign in the specified and combination
/// as an asynchronous operation.
///
/// The user name to sign in.
/// The password to attempt to sign in with.
/// Flag indicating whether the sign-in cookie should persist after the browser is closed.
/// Flag indicating if the user account should be locked if the sign in fails.
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
public virtual async Task PasswordSignInAsync(string userName, string password,
bool isPersistent, bool lockoutOnFailure)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
}
///
/// Attempts a password sign in for a user.
///
/// The user to sign in.
/// The password to attempt to sign in with.
/// Flag indicating if the user account should be locked if the sign in fails.
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
///
public virtual async Task CheckPasswordSignInAsync(TUser user, string password, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
if (await IsLockedOut(user))
{
return await LockedOut(user);
}
if (await UserManager.CheckPasswordAsync(user, password))
{
await ResetLockout(user);
return SignInResult.Success;
}
Logger.LogWarning(2, "User {userId} failed to provide the correct password.", await UserManager.GetUserIdAsync(user));
if (UserManager.SupportsUserLockout && lockoutOnFailure)
{
// If lockout is requested, increment access failed count which might lock out the user
await UserManager.AccessFailedAsync(user);
if (await UserManager.IsLockedOutAsync(user))
{
return await LockedOut(user);
}
}
return SignInResult.Failed;
}
///
/// Returns a flag indicating if the current client browser has been remembered by two factor authentication
/// for the user attempting to login, as an asynchronous operation.
///
/// The user attempting to login.
///
/// The task object representing the asynchronous operation containing true if the browser has been remembered
/// for the current user.
///
public virtual async Task IsTwoFactorClientRememberedAsync(TUser user)
{
var userId = await UserManager.GetUserIdAsync(user);
var result = await Context.Authentication.AuthenticateAsync(Options.Cookies.TwoFactorRememberMeCookieAuthenticationScheme);
return (result != null && result.FindFirstValue(ClaimTypes.Name) == userId);
}
///
/// Sets a flag on the browser to indicate the user has selected "Remember this browser" for two factor authentication purposes,
/// as an asynchronous operation.
///
/// The user who choose "remember this browser".
/// The task object representing the asynchronous operation.
public virtual async Task RememberTwoFactorClientAsync(TUser user)
{
var userId = await UserManager.GetUserIdAsync(user);
var rememberBrowserIdentity = new ClaimsIdentity(Options.Cookies.TwoFactorRememberMeCookieAuthenticationScheme);
rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.Name, userId));
await Context.Authentication.SignInAsync(Options.Cookies.TwoFactorRememberMeCookieAuthenticationScheme,
new ClaimsPrincipal(rememberBrowserIdentity),
new AuthenticationProperties { IsPersistent = true });
}
///
/// Clears the "Remember this browser flag" from the current browser, as an asynchronous operation.
///
/// The task object representing the asynchronous operation.
public virtual Task ForgetTwoFactorClientAsync()
{
return Context.Authentication.SignOutAsync(Options.Cookies.TwoFactorRememberMeCookieAuthenticationScheme);
}
///
/// Validates the two faction sign in code and creates and signs in the user, as an asynchronous operation.
///
/// The two factor authentication provider to validate the code against.
/// The two factor authentication code to validate.
/// Flag indicating whether the sign-in cookie should persist after the browser is closed.
/// Flag indicating whether the current browser should be remember, suppressing all further
/// two factor authentication prompts.
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent,
bool rememberClient)
{
var twoFactorInfo = await RetrieveTwoFactorInfoAsync();
if (twoFactorInfo == null || twoFactorInfo.UserId == null)
{
return SignInResult.Failed;
}
var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId);
if (user == null)
{
return SignInResult.Failed;
}
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code))
{
// When token is verified correctly, clear the access failed count used for lockout
await ResetLockout(user);
// Cleanup external cookie
if (twoFactorInfo.LoginProvider != null)
{
await Context.Authentication.SignOutAsync(Options.Cookies.ExternalCookieAuthenticationScheme);
}
// Cleanup two factor user id cookie
await Context.Authentication.SignOutAsync(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme);
if (rememberClient)
{
await RememberTwoFactorClientAsync(user);
}
await UserManager.ResetAccessFailedCountAsync(user);
await SignInAsync(user, isPersistent, twoFactorInfo.LoginProvider);
return SignInResult.Success;
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user);
return SignInResult.Failed;
}
///
/// Gets the for the current two factor authentication login, as an asynchronous operation.
///
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
public virtual async Task GetTwoFactorAuthenticationUserAsync()
{
var info = await RetrieveTwoFactorInfoAsync();
if (info == null)
{
return null;
}
return await UserManager.FindByIdAsync(info.UserId);
}
///
/// Signs in a user via a previously registered third party login, as an asynchronous operation.
///
/// The login provider to use.
/// The unique provider identifier for the user.
/// Flag indicating whether the sign-in cookie should persist after the browser is closed.
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
public virtual async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent)
{
var user = await UserManager.FindByLoginAsync(loginProvider, providerKey);
if (user == null)
{
return SignInResult.Failed;
}
var error = await PreSignInCheck(user);
if (error != null)
{
return error;
}
return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider);
}
///
/// Gets a collection of s for the known external login providers.
///
/// A collection of s for the known external login providers.
public virtual IEnumerable GetExternalAuthenticationSchemes()
{
return Context.Authentication.GetAuthenticationSchemes().Where(d => !string.IsNullOrEmpty(d.DisplayName));
}
///
/// Gets the external login information for the current login, as an asynchronous operation.
///
/// Flag indication whether a Cross Site Request Forgery token was expected in the current request.
/// The task object representing the asynchronous operation containing the
/// for the sign-in attempt.
public virtual async Task GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = new AuthenticateContext(Options.Cookies.ExternalCookieAuthenticationScheme);
await Context.Authentication.AuthenticateAsync(auth);
if (auth.Principal == null || auth.Properties == null || !auth.Properties.ContainsKey(LoginProviderKey))
{
return null;
}
if (expectedXsrf != null)
{
if (!auth.Properties.ContainsKey(XsrfKey))
{
return null;
}
var userId = auth.Properties[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = auth.Properties[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
return new ExternalLoginInfo(auth.Principal, provider, providerKey, new AuthenticationDescription(auth.Description).DisplayName)
{
AuthenticationTokens = new AuthenticationProperties(auth.Properties).GetTokens()
};
}
///
/// Stores any authentication tokens found in the external authentication cookie into the associated user.
///
/// The information from the external login provider.
/// The that represents the asynchronous operation, containing the of the operation.
public virtual async Task UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin)
{
if (externalLogin == null)
{
throw new ArgumentNullException(nameof(externalLogin));
}
if (externalLogin.AuthenticationTokens != null && externalLogin.AuthenticationTokens.Any())
{
var user = await UserManager.FindByLoginAsync(externalLogin.LoginProvider, externalLogin.ProviderKey);
if (user == null)
{
return IdentityResult.Failed();
}
foreach (var token in externalLogin.AuthenticationTokens)
{
var result = await UserManager.SetAuthenticationTokenAsync(user, externalLogin.LoginProvider, token.Name, token.Value);
if (!result.Succeeded)
{
return result;
}
}
}
return IdentityResult.Success;
}
///
/// Configures the redirect URL and user identifier for the specified external login .
///
/// The provider to configure.
/// The external login URL users should be redirected to during the login flow.
/// The current user's identifier, which will be used to provide CSRF protection.
/// A configured .
public virtual AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null)
{
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
properties.Items[LoginProviderKey] = provider;
if (userId != null)
{
properties.Items[XsrfKey] = userId;
}
return properties;
}
///
/// Creates a claims principal for the specified 2fa information.
///
/// The user whose is logging in via 2fa.
/// The 2fa provider.
/// A containing the user 2fa information.
internal ClaimsPrincipal StoreTwoFactorInfo(string userId, string loginProvider)
{
var identity = new ClaimsIdentity(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, userId));
if (loginProvider != null)
{
identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, loginProvider));
}
return new ClaimsPrincipal(identity);
}
private ClaimsIdentity CreateIdentity(TwoFactorAuthenticationInfo info)
{
if (info == null)
{
return null;
}
var identity = new ClaimsIdentity(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, info.UserId));
if (info.LoginProvider != null)
{
identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, info.LoginProvider));
}
return identity;
}
private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, string loginProvider = null)
{
if (UserManager.SupportsUserTwoFactor &&
await UserManager.GetTwoFactorEnabledAsync(user) &&
(await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0)
{
if (!await IsTwoFactorClientRememberedAsync(user))
{
// Store the userId for use after two factor check
var userId = await UserManager.GetUserIdAsync(user);
await Context.Authentication.SignInAsync(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme, StoreTwoFactorInfo(userId, loginProvider));
return SignInResult.TwoFactorRequired;
}
}
// Cleanup external cookie
if (loginProvider != null)
{
await Context.Authentication.SignOutAsync(Options.Cookies.ExternalCookieAuthenticationScheme);
}
await SignInAsync(user, isPersistent, loginProvider);
return SignInResult.Success;
}
private async Task RetrieveTwoFactorInfoAsync()
{
var result = await Context.Authentication.AuthenticateAsync(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme);
if (result != null)
{
return new TwoFactorAuthenticationInfo
{
UserId = result.FindFirstValue(ClaimTypes.Name),
LoginProvider = result.FindFirstValue(ClaimTypes.AuthenticationMethod)
};
}
return null;
}
private async Task IsLockedOut(TUser user)
{
return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user);
}
private async Task LockedOut(TUser user)
{
Logger.LogWarning(3, "User {userId} is currently locked out.", await UserManager.GetUserIdAsync(user));
return SignInResult.LockedOut;
}
private async Task PreSignInCheck(TUser user)
{
if (!await CanSignInAsync(user))
{
return SignInResult.NotAllowed;
}
if (await IsLockedOut(user))
{
return await LockedOut(user);
}
return null;
}
private Task ResetLockout(TUser user)
{
if (UserManager.SupportsUserLockout)
{
return UserManager.ResetAccessFailedCountAsync(user);
}
return Task.FromResult(0);
}
internal class TwoFactorAuthenticationInfo
{
public string UserId { get; set; }
public string LoginProvider { get; set; }
}
}
}