2021-03-29 17:37:58 +11:00
using System.Security.Claims ;
using Microsoft.AspNetCore.Authentication ;
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.Identity ;
using Microsoft.Extensions.Logging ;
using Microsoft.Extensions.Options ;
Implements Public Access in netcore (#10137)
* Getting new netcore PublicAccessChecker in place
* Adds full test coverage for PublicAccessChecker
* remove PublicAccessComposer
* adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller
* Implements the required methods on IMemberManager, removes old migrated code
* Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops
* adds note
* adds note
* Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling.
* Changes name to IUmbracoEndpointBuilder
* adds note
* Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect
* fixing build
* Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware.
* adds note
* removes unused filter, fixes build
* fixes WebPath and tests
* Looks up entities in one query
* remove usings
* Fix test, remove stylesheet
* Set status code before we write to response to avoid error
* Ensures that users and members are validated when logging in. Shares more code between users and members.
* Fixes RepositoryCacheKeys to ensure the keys are normalized
* oops didn't mean to commit this
* Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy
* bah, far out this keeps getting recommitted. sorry
Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 15:11:45 +10:00
using Umbraco.Cms.Core.Security ;
2021-03-29 17:37:58 +11:00
using Umbraco.Extensions ;
2022-05-09 09:39:46 +02:00
namespace Umbraco.Cms.Web.Common.Security ;
/// <summary>
/// Abstract sign in manager implementation allowing modifying all defeault authentication schemes
/// </summary>
/// <typeparam name="TUser"></typeparam>
public abstract class UmbracoSignInManager < TUser > : SignInManager < TUser >
where TUser : UmbracoIdentityUser
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
protected const string UmbracoSignInMgrLoginProviderKey = "LoginProvider" ;
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
protected const string UmbracoSignInMgrXsrfKey = "XsrfId" ;
public UmbracoSignInManager (
UserManager < TUser > userManager ,
IHttpContextAccessor contextAccessor ,
IUserClaimsPrincipalFactory < TUser > claimsFactory ,
IOptions < IdentityOptions > optionsAccessor ,
ILogger < SignInManager < TUser > > logger ,
IAuthenticationSchemeProvider schemes ,
IUserConfirmation < TUser > confirmation )
: base ( userManager , contextAccessor , claimsFactory , optionsAccessor , logger , schemes , confirmation )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
}
protected abstract string AuthenticationType { get ; }
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
protected abstract string ExternalAuthenticationType { get ; }
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
protected abstract string TwoFactorAuthenticationType { get ; }
protected abstract string TwoFactorRememberMeAuthenticationType { get ; }
/// <inheritdoc />
public override async Task < SignInResult > PasswordSignInAsync ( TUser user , string password , bool isPersistent , bool lockoutOnFailure )
{
// override to handle logging/events
SignInResult result = await base . PasswordSignInAsync ( user , password , isPersistent , lockoutOnFailure ) ;
return await HandleSignIn ( user , user . UserName , result ) ;
}
/// <inheritdoc />
public override async Task < ExternalLoginInfo ? > GetExternalLoginInfoAsync ( string? expectedXsrf = null )
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L422
// to replace the auth scheme
AuthenticateResult auth = await Context . AuthenticateAsync ( ExternalAuthenticationType ) ;
IDictionary < string , string? > ? items = auth . Properties ? . Items ;
if ( auth . Principal = = null | | items = = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
Logger . LogDebug (
"The external login authentication failed. No user Principal or authentication items was resolved." ) ;
return null ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
if ( ! items . ContainsKey ( UmbracoSignInMgrLoginProviderKey ) )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
throw new InvalidOperationException (
$"The external login authenticated successfully but the key {UmbracoSignInMgrLoginProviderKey} was not found in the authentication properties. Ensure you call SignInManager.ConfigureExternalAuthenticationProperties before issuing a ChallengeResult." ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
if ( expectedXsrf ! = null )
{
if ( ! items . ContainsKey ( UmbracoSignInMgrXsrfKey ) )
2021-03-29 17:37:58 +11:00
{
return null ;
}
2022-05-09 09:39:46 +02:00
var userId = items [ UmbracoSignInMgrXsrfKey ] ;
if ( userId ! = expectedXsrf )
2021-03-29 17:37:58 +11:00
{
return null ;
}
2022-05-09 09:39:46 +02:00
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
var providerKey = auth . Principal . FindFirstValue ( ClaimTypes . NameIdentifier ) ;
var provider = items [ UmbracoSignInMgrLoginProviderKey ] ;
2022-09-19 16:37:24 +02:00
if ( providerKey is null | | provider is null )
2022-05-09 09:39:46 +02:00
{
return null ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
var providerDisplayName =
( await GetExternalAuthenticationSchemesAsync ( ) ) . FirstOrDefault ( p = > p . Name = = provider ) ? . DisplayName ? ?
provider ;
return new ExternalLoginInfo ( auth . Principal , provider , providerKey , providerDisplayName )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
AuthenticationTokens = auth . Properties ? . GetTokens ( ) ,
AuthenticationProperties = auth . Properties ,
} ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
2022-09-19 16:37:24 +02:00
public override async Task < TUser ? > GetTwoFactorAuthenticationUserAsync ( )
2022-05-09 09:39:46 +02:00
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// replaced in order to use a custom auth type
TwoFactorAuthenticationInfo ? info = await RetrieveTwoFactorInfoAsync ( ) ;
2022-09-19 16:37:24 +02:00
if ( info ? . UserId is null )
2022-05-09 09:39:46 +02:00
{
2022-09-19 16:37:24 +02:00
return null ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
return await UserManager . FindByIdAsync ( info . UserId ) ;
}
/// <inheritdoc />
public override async Task < SignInResult > PasswordSignInAsync ( string userName , string password , bool isPersistent , bool lockoutOnFailure )
{
// override to handle logging/events
TUser ? user = await UserManager . FindByNameAsync ( userName ) ;
if ( user = = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
return await HandleSignIn ( null , userName , SignInResult . Failed ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
return await PasswordSignInAsync ( user , password , isPersistent , lockoutOnFailure ) ;
}
/// <inheritdoc />
public override bool IsSignedIn ( ClaimsPrincipal principal )
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L126
// replaced in order to use a custom auth type
if ( principal = = null )
{
throw new ArgumentNullException ( nameof ( principal ) ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
return principal . Identities . Any ( i = > i . AuthenticationType = = AuthenticationType ) ;
}
/// <inheritdoc />
2022-09-19 16:37:24 +02:00
public override async Task < SignInResult > TwoFactorSignInAsync ( string provider , string code , bool isPersistent , bool rememberClient )
2022-05-09 09:39:46 +02:00
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L552
// replaced in order to use a custom auth type and to implement logging/events
TwoFactorAuthenticationInfo ? twoFactorInfo = await RetrieveTwoFactorInfoAsync ( ) ;
if ( twoFactorInfo = = null | | twoFactorInfo . UserId = = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
return SignInResult . Failed ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
TUser ? user = await UserManager . FindByIdAsync ( twoFactorInfo . UserId ) ;
if ( user = = null )
{
return SignInResult . Failed ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
SignInResult ? error = await PreSignInCheck ( user ) ;
if ( error ! = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
return error ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
if ( await UserManager . VerifyTwoFactorTokenAsync ( user , provider , code ) )
{
await DoTwoFactorSignInAsync ( user , twoFactorInfo , isPersistent , rememberClient ) ;
return await HandleSignIn ( user , user . UserName , SignInResult . Success ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager . AccessFailedAsync ( user ) ;
return await HandleSignIn ( user , user . UserName , SignInResult . Failed ) ;
}
/// <inheritdoc />
public override async Task RefreshSignInAsync ( TUser user )
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L126
// replaced in order to use a custom auth type
AuthenticateResult auth = await Context . AuthenticateAsync ( AuthenticationType ) ;
IList < Claim > claims = Array . Empty < Claim > ( ) ;
Claim ? authenticationMethod = auth . Principal ? . FindFirst ( ClaimTypes . AuthenticationMethod ) ;
Claim ? amr = auth . Principal ? . FindFirst ( "amr" ) ;
if ( authenticationMethod ! = null | | amr ! = null )
{
claims = new List < Claim > ( ) ;
if ( authenticationMethod ! = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
claims . Add ( authenticationMethod ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
if ( amr ! = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
claims . Add ( amr ) ;
2021-03-29 17:37:58 +11:00
}
}
2022-05-09 09:39:46 +02:00
await SignInWithClaimsAsync ( user , auth . Properties , claims ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
public override async Task SignInWithClaimsAsync ( TUser user , AuthenticationProperties ? authenticationProperties , IEnumerable < Claim > additionalClaims )
{
// override to replace IdentityConstants.ApplicationScheme with custom AuthenticationType
// code taken from aspnetcore: https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// we also override to set the current HttpContext principal since this isn't done by default
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
// we also need to call our handle login to ensure all date/events are set
await HandleSignIn ( user , user . UserName , SignInResult . Success ) ;
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
ClaimsPrincipal ? userPrincipal = await CreateUserPrincipalAsync ( user ) ;
foreach ( Claim claim in additionalClaims )
{
userPrincipal . Identities . First ( ) . AddClaim ( claim ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
// FYI (just for informational purposes):
// This calls an ext method will eventually reaches `IAuthenticationService.SignInAsync`
// which then resolves the `IAuthenticationSignInHandler` for the current scheme
// by calling `IAuthenticationHandlerProvider.GetHandlerAsync(context, scheme);`
// which then calls `IAuthenticationSignInHandler.SignInAsync` = CookieAuthenticationHandler.HandleSignInAsync
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
// Also note, that when the CookieAuthenticationHandler sign in is successful we handle that event within our
// own ConfigureUmbracoBackOfficeCookieOptions which assigns the current HttpContext.User to the IPrincipal created
2021-07-26 13:12:29 -06:00
2022-05-09 09:39:46 +02:00
// Also note, this method gets called when performing 2FA logins
await Context . SignInAsync (
AuthenticationType ,
userPrincipal ,
authenticationProperties ? ? new AuthenticationProperties ( ) ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
public override async Task SignOutAsync ( )
{
2023-06-12 09:56:04 +02:00
// Update the security stamp to sign out everywhere.
TUser ? user = await UserManager . GetUserAsync ( Context . User ) ;
if ( user is not null )
{
await UserManager . UpdateSecurityStampAsync ( user ) ;
}
2022-05-09 09:39:46 +02:00
// override to replace IdentityConstants.ApplicationScheme with custom auth types
// code taken from aspnetcore: https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
await Context . SignOutAsync ( AuthenticationType ) ;
await Context . SignOutAsync ( ExternalAuthenticationType ) ;
await Context . SignOutAsync ( TwoFactorAuthenticationType ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
public override async Task < bool > IsTwoFactorClientRememberedAsync ( TUser user )
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L422
// to replace the auth scheme
var userId = await UserManager . GetUserIdAsync ( user ) ;
AuthenticateResult result = await Context . AuthenticateAsync ( TwoFactorRememberMeAuthenticationType ) ;
return result . Principal ! = null & & result . Principal . FindFirstValue ( ClaimTypes . Name ) = = userId ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
public override async Task RememberTwoFactorClientAsync ( TUser user )
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L422
// to replace the auth scheme
ClaimsPrincipal principal = await StoreRememberClient ( user ) ;
await Context . SignInAsync (
TwoFactorRememberMeAuthenticationType ,
principal ,
new AuthenticationProperties { IsPersistent = true } ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
public override async Task < SignInResult > TwoFactorRecoveryCodeSignInAsync ( string recoveryCode )
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L422
// to replace the auth scheme
TwoFactorAuthenticationInfo ? twoFactorInfo = await RetrieveTwoFactorInfoAsync ( ) ;
if ( twoFactorInfo = = null | | twoFactorInfo . UserId = = null )
{
return SignInResult . Failed ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
TUser ? user = await UserManager . FindByIdAsync ( twoFactorInfo . UserId ) ;
if ( user = = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
return SignInResult . Failed ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
IdentityResult ? result = await UserManager . RedeemTwoFactorRecoveryCodeAsync ( user , recoveryCode ) ;
if ( result . Succeeded )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
await DoTwoFactorSignInAsync ( user , twoFactorInfo , false , false ) ;
return SignInResult . Success ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
// We don't protect against brute force attacks since codes are expected to be random.
return SignInResult . Failed ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
public override Task ForgetTwoFactorClientAsync ( ) = >
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L422
// to replace the auth scheme
Context . SignOutAsync ( TwoFactorRememberMeAuthenticationType ) ;
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <summary>
/// Called on any login attempt to update the AccessFailedCount and to raise events
/// </summary>
/// <param name="user"></param>
/// <param name="username"></param>
/// <param name="result"></param>
/// <returns></returns>
protected virtual async Task < SignInResult > HandleSignIn ( TUser ? user , string? username , SignInResult result )
{
// TODO: Here I believe we can do all (or most) of the usermanager event raising so that it is not in the AuthenticationController
if ( username . IsNullOrWhiteSpace ( ) )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
username = "UNKNOWN" ; // could happen in 2fa or something else weird
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
if ( result . Succeeded )
{
// track the last login date
user ! . LastLoginDateUtc = DateTime . UtcNow ;
if ( user . AccessFailedCount > 0 )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
// we have successfully logged in, reset the AccessFailedCount
user . AccessFailedCount = 0 ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
await UserManager . UpdateAsync ( user ) ;
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
Logger . LogInformation ( "User: {UserName} logged in from IP address {IpAddress}" , username , Context . Connection . RemoteIpAddress ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
else if ( result . IsLockedOut )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
Logger . LogInformation (
"Login attempt failed for username {UserName} from IP address {IpAddress}, the user is locked" ,
username ,
Context . Connection . RemoteIpAddress ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
else if ( result . RequiresTwoFactor )
{
Logger . LogInformation (
"Login attempt requires verification for username {UserName} from IP address {IpAddress}" ,
username ,
Context . Connection . RemoteIpAddress ) ;
}
else if ( ! result . Succeeded | | result . IsNotAllowed )
{
Logger . LogInformation (
"Login attempt failed for username {UserName} from IP address {IpAddress}" ,
username ,
Context . Connection . RemoteIpAddress ) ;
}
else
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
throw new ArgumentOutOfRangeException ( ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
return result ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
protected override async Task < SignInResult > SignInOrTwoFactorAsync ( TUser user , bool isPersistent , string? loginProvider = null , bool bypassTwoFactor = false )
{
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// to replace custom auth types
if ( ! bypassTwoFactor & & await IsTfaEnabled ( user ) )
{
if ( ! await IsTwoFactorClientRememberedAsync ( user ) )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
// Store the userId for use after two factor check
var userId = await UserManager . GetUserIdAsync ( user ) ;
await Context . SignInAsync ( TwoFactorAuthenticationType , StoreTwoFactorInfo ( userId , loginProvider ) ) ;
return SignInResult . TwoFactorRequired ;
2021-03-29 17:37:58 +11:00
}
}
2022-05-09 09:39:46 +02:00
// Cleanup external cookie
if ( loginProvider ! = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
await Context . SignOutAsync ( ExternalAuthenticationType ) ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
if ( loginProvider = = null )
{
await SignInWithClaimsAsync ( user , isPersistent , new [ ] { new Claim ( "amr" , "pwd" ) } ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
else
{
await SignInAsync ( user , isPersistent , loginProvider ) ;
}
return SignInResult . Success ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L782
// since it's not public
private async Task < bool > IsTfaEnabled ( TUser user )
= > UserManager . SupportsUserTwoFactor & &
await UserManager . GetTwoFactorEnabledAsync ( user ) & &
( await UserManager . GetValidTwoFactorProvidersAsync ( user ) ) . Count > 0 ;
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L743
// to replace custom auth types
private ClaimsPrincipal StoreTwoFactorInfo ( string userId , string? loginProvider )
{
var identity = new ClaimsIdentity ( TwoFactorAuthenticationType ) ;
identity . AddClaim ( new Claim ( ClaimTypes . Name , userId ) ) ;
if ( loginProvider ! = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
identity . AddClaim ( new Claim ( ClaimTypes . AuthenticationMethod , loginProvider ) ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
return new ClaimsPrincipal ( identity ) ;
}
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// copy is required in order to use custom auth types
private async Task < ClaimsPrincipal > StoreRememberClient ( TUser user )
{
var userId = await UserManager . GetUserIdAsync ( user ) ;
var rememberBrowserIdentity = new ClaimsIdentity ( TwoFactorRememberMeAuthenticationType ) ;
rememberBrowserIdentity . AddClaim ( new Claim ( ClaimTypes . Name , userId ) ) ;
if ( UserManager . SupportsUserSecurityStamp )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
var stamp = await UserManager . GetSecurityStampAsync ( user ) ;
rememberBrowserIdentity . AddClaim ( new Claim ( Options . ClaimsIdentity . SecurityStampClaimType , stamp ) ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
return new ClaimsPrincipal ( rememberBrowserIdentity ) ;
}
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// copy is required in order to use a custom auth type
private async Task < TwoFactorAuthenticationInfo ? > RetrieveTwoFactorInfoAsync ( )
{
AuthenticateResult result = await Context . AuthenticateAsync ( TwoFactorAuthenticationType ) ;
if ( result . Principal ! = null )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
return new TwoFactorAuthenticationInfo
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
UserId = result . Principal . FindFirstValue ( ClaimTypes . Name ) ,
LoginProvider = result . Principal . FindFirstValue ( ClaimTypes . AuthenticationMethod ) ,
} ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
return null ;
}
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
// copy is required in order to use custom auth types
private async Task DoTwoFactorSignInAsync ( TUser user , TwoFactorAuthenticationInfo twoFactorInfo , bool isPersistent , bool rememberClient )
{
// When token is verified correctly, clear the access failed count used for lockout
await ResetLockout ( user ) ;
2021-03-29 17:37:58 +11:00
2022-05-09 09:39:46 +02:00
var claims = new List < Claim > { new ( "amr" , "mfa" ) } ;
// Cleanup external cookie
if ( twoFactorInfo . LoginProvider ! = null )
{
claims . Add ( new Claim ( ClaimTypes . AuthenticationMethod , twoFactorInfo . LoginProvider ) ) ;
await Context . SignOutAsync ( ExternalAuthenticationType ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
// Cleanup two factor user id cookie
await Context . SignOutAsync ( TwoFactorAuthenticationType ) ;
if ( rememberClient )
2021-03-29 17:37:58 +11:00
{
2022-05-09 09:39:46 +02:00
await RememberTwoFactorClientAsync ( user ) ;
2021-03-29 17:37:58 +11:00
}
2022-05-09 09:39:46 +02:00
await SignInWithClaimsAsync ( user , isPersistent , claims ) ;
}
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L891
private class TwoFactorAuthenticationInfo
{
public string? UserId { get ; set ; }
public string? LoginProvider { get ; set ; }
2021-03-29 17:37:58 +11:00
}
}