Add support for Authentication Tokens to Identity
This commit is contained in:
parent
c8849685cf
commit
82863e9444
|
|
@ -172,6 +172,9 @@ namespace IdentitySample.Controllers
|
|||
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
// Update any authentication tokens if login succeeded
|
||||
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
|
||||
|
||||
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
|
|
@ -188,7 +191,7 @@ namespace IdentitySample.Controllers
|
|||
// If the user does not have an account, then ask the user to create an account.
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["LoginProvider"] = info.LoginProvider;
|
||||
var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
|
||||
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
||||
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
|
||||
}
|
||||
}
|
||||
|
|
@ -217,6 +220,10 @@ namespace IdentitySample.Controllers
|
|||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
|
||||
|
||||
// Update any authentication tokens as well
|
||||
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
|
||||
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,11 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
/// </summary>
|
||||
public DbSet<IdentityUserRole<TKey>> UserRoles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DbSet{TEntity}"/> of User tokens.
|
||||
/// </summary>
|
||||
public DbSet<IdentityUserToken<TKey>> UserTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
|
||||
/// </summary>
|
||||
|
|
@ -207,6 +212,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
builder.Entity<IdentityUserToken<TKey>>(b =>
|
||||
{
|
||||
b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name });
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an authentication token for a user.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the primary key used for users.</typeparam>
|
||||
public class IdentityUserToken<TKey> where TKey : IEquatable<TKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the primary key of the user that the token belongs to.
|
||||
/// </summary>
|
||||
public virtual TKey UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LoginProvider this token is from.
|
||||
/// </summary>
|
||||
public virtual string LoginProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the token.
|
||||
/// </summary>
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the token value.
|
||||
/// </summary>
|
||||
public virtual string Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
IUserLockoutStore<TUser>,
|
||||
IUserPhoneNumberStore<TUser>,
|
||||
IQueryableUserStore<TUser>,
|
||||
IUserTwoFactorStore<TUser>
|
||||
IUserTwoFactorStore<TUser>,
|
||||
IUserAuthenticationTokenStore<TUser>
|
||||
where TUser : IdentityUser<TKey>
|
||||
where TRole : IdentityRole<TKey>
|
||||
where TContext : DbContext
|
||||
|
|
@ -523,6 +524,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
private DbSet<IdentityUserClaim<TKey>> UserClaims { get { return Context.Set<IdentityUserClaim<TKey>>(); } }
|
||||
private DbSet<IdentityUserRole<TKey>> UserRoles { get { return Context.Set<IdentityUserRole<TKey>>(); } }
|
||||
private DbSet<IdentityUserLogin<TKey>> UserLogins { get { return Context.Set<IdentityUserLogin<TKey>>(); } }
|
||||
private DbSet<IdentityUserToken<TKey>> UserTokens { get { return Context.Set<IdentityUserToken<TKey>>(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Get the claims associated with the specified <paramref name="user"/> as an asynchronous operation.
|
||||
|
|
@ -653,7 +655,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
LoginProvider = login.LoginProvider,
|
||||
ProviderDisplayName = login.ProviderDisplayName
|
||||
};
|
||||
// TODO: fixup so we don't have to update both
|
||||
UserLogins.Add(l);
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
|
@ -1196,5 +1197,69 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
|
|||
}
|
||||
return new List<TUser>();
|
||||
}
|
||||
|
||||
private Task<IdentityUserToken<TKey>> FindToken(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = user.Id;
|
||||
return UserTokens.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.Name == name, cancellationToken);
|
||||
}
|
||||
|
||||
// <inheritdoc>
|
||||
public virtual async Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
var token = await FindToken(user, loginProvider, name, cancellationToken);
|
||||
if (token == null)
|
||||
{
|
||||
UserTokens.Add(new IdentityUserToken<TKey>
|
||||
{
|
||||
UserId = user.Id,
|
||||
LoginProvider = loginProvider,
|
||||
Name = name,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
token.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
var userId = user.Id;
|
||||
var entry = await UserTokens.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.Name == name, cancellationToken);
|
||||
if (entry != null)
|
||||
{
|
||||
UserTokens.Remove(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
var entry = await FindToken(user, loginProvider, name, cancellationToken);
|
||||
return entry?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// Provides protection and validation of identity tokens.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser">The type used to represent a user.</typeparam>
|
||||
public class DataProtectorTokenProvider<TUser> : IUserTokenProvider<TUser> where TUser : class
|
||||
public class DataProtectorTokenProvider<TUser> : IUserTwoFactorTokenProvider<TUser> where TUser : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataProtectorTokenProvider{TUser}"/> class.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity
|
||||
{
|
||||
|
|
@ -13,20 +15,22 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ExternalLoginInfo"/>
|
||||
/// </summary>
|
||||
/// <param name="externalPrincipal">The <see cref="ClaimsPrincipal"/> to associate with this login.</param>
|
||||
/// <param name="principal">The <see cref="ClaimsPrincipal"/> to associate with this login.</param>
|
||||
/// <param name="loginProvider">The provider associated with this login information.</param>
|
||||
/// <param name="providerKey">The unique identifier for this user provided by the login provider.</param>
|
||||
/// <param name="displayName">The display name for this user provided by the login provider.</param>
|
||||
public ExternalLoginInfo(ClaimsPrincipal externalPrincipal, string loginProvider, string providerKey,
|
||||
public ExternalLoginInfo(ClaimsPrincipal principal, string loginProvider, string providerKey,
|
||||
string displayName) : base(loginProvider, providerKey, displayName)
|
||||
{
|
||||
ExternalPrincipal = externalPrincipal;
|
||||
Principal = principal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ClaimsPrincipal"/> associated with this login.
|
||||
/// </summary>
|
||||
/// <value>The <see cref="ClaimsPrincipal"/> associated with this login.</value>
|
||||
public ClaimsPrincipal ExternalPrincipal { get; set; }
|
||||
public ClaimsPrincipal Principal { get; set; }
|
||||
|
||||
public IEnumerable<AuthenticationToken> AuthenticationTokens { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an abstraction to store a user's authentication tokens.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
||||
public interface IUserAuthenticationTokenStore<TUser> : IUserStore<TUser> where TUser : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the token value for a particular user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="loginProvider">The authentication provider for the token.</param>
|
||||
/// <param name="name">The name of the token.</param>
|
||||
/// <param name="value">The value of the token.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||
Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a token for a user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="loginProvider">The authentication provider for the token.</param>
|
||||
/// <param name="name">The name of the token.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||
Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the token value.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="loginProvider">The authentication provider for the token.</param>
|
||||
/// <param name="name">The name of the token.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||
Task<string> GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,10 +31,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// <param name="loginProvider">The login provide whose information should be removed.</param>
|
||||
/// <param name="providerKey">The key given by the external login provider for the specified user.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="Task"/> that contains a flag the result of the asynchronous removing operation. The flag will be true if
|
||||
/// the login information was existed and removed, otherwise false.
|
||||
/// </returns>
|
||||
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
||||
Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ using System.Threading.Tasks;
|
|||
namespace Microsoft.AspNetCore.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an abstraction for token generators.
|
||||
/// Provides an abstraction for two factor token generators.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
||||
public interface IUserTokenProvider<TUser> where TUser : class
|
||||
public interface IUserTwoFactorTokenProvider<TUser> where TUser : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a token for the specified <paramref name="user"/> and <paramref name="purpose"/>.
|
||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// and validated it with the same purpose a token with the purpose of TOTP would not pass the heck even if it was
|
||||
/// for the same user.
|
||||
///
|
||||
/// Implementations of <see cref="IUserTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
||||
/// Implementations of <see cref="IUserTwoFactorTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
||||
/// help with token separation.
|
||||
/// </remarks>
|
||||
Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user);
|
||||
|
|
@ -133,11 +133,11 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// Adds a token provider for the <seealso cref="UserType"/>.
|
||||
/// </summary>
|
||||
/// <param name="providerName">The name of the provider to add.</param>
|
||||
/// <param name="provider">The type of the <see cref="IUserTokenProvider{TUser}"/> to add.</param>
|
||||
/// <param name="provider">The type of the <see cref="IUserTwoFactorTokenProvider{TUser}"/> to add.</param>
|
||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||
public virtual IdentityBuilder AddTokenProvider(string providerName, Type provider)
|
||||
{
|
||||
if (!typeof(IUserTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo()))
|
||||
if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo()))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatInvalidManagerType(provider.Name, "IUserTokenProvider", UserType.Name));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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;
|
||||
|
|
@ -460,8 +461,43 @@ namespace Microsoft.AspNetCore.Identity
|
|||
{
|
||||
return null;
|
||||
}
|
||||
// REVIEW: fix this wrap
|
||||
return new ExternalLoginInfo(auth.Principal, provider, providerKey, new AuthenticationDescription(auth.Description).DisplayName);
|
||||
return new ExternalLoginInfo(auth.Principal, provider, providerKey, new AuthenticationDescription(auth.Description).DisplayName)
|
||||
{
|
||||
AuthenticationTokens = new AuthenticationProperties(auth.Properties).GetTokens()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores any authentication tokens found in the external authentication cookie into the associated user.
|
||||
/// </summary>
|
||||
/// <param name="externalLogin">The information from the external login provider.</param>
|
||||
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the operation.</returns>
|
||||
public virtual async Task<IdentityResult> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// Represents a token provider that generates time based codes using the user's security stamp.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUser">The type encapsulating a user.</typeparam>
|
||||
public abstract class TotpSecurityStampBasedTokenProvider<TUser> : IUserTokenProvider<TUser>
|
||||
public abstract class TotpSecurityStampBasedTokenProvider<TUser> : IUserTwoFactorTokenProvider<TUser>
|
||||
where TUser : class
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// and validated it with the same purpose a token with the purpose of TOTP would not pass the check even if it was
|
||||
/// for the same user.
|
||||
///
|
||||
/// Implementations of <see cref="IUserTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
||||
/// Implementations of <see cref="IUserTwoFactorTokenProvider{TUser}"/> should validate that purpose is not null or empty to
|
||||
/// help with token separation.
|
||||
/// </remarks>
|
||||
public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ namespace Microsoft.AspNetCore.Identity
|
|||
protected const string ResetPasswordTokenPurpose = "ResetPassword";
|
||||
protected const string ConfirmEmailTokenPurpose = "EmailConfirmation";
|
||||
|
||||
private readonly Dictionary<string, IUserTokenProvider<TUser>> _tokenProviders =
|
||||
new Dictionary<string, IUserTokenProvider<TUser>>();
|
||||
private readonly Dictionary<string, IUserTwoFactorTokenProvider<TUser>> _tokenProviders =
|
||||
new Dictionary<string, IUserTwoFactorTokenProvider<TUser>>();
|
||||
|
||||
private TimeSpan _defaultLockout = TimeSpan.Zero;
|
||||
private bool _disposed;
|
||||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
_context = services.GetService<IHttpContextAccessor>()?.HttpContext;
|
||||
foreach (var providerName in Options.Tokens.ProviderMap.Keys)
|
||||
{
|
||||
var provider = services.GetRequiredService(Options.Tokens.ProviderMap[providerName].ProviderType) as IUserTokenProvider<TUser>;
|
||||
var provider = services.GetRequiredService(Options.Tokens.ProviderMap[providerName].ProviderType) as IUserTwoFactorTokenProvider<TUser>;
|
||||
if (provider != null)
|
||||
{
|
||||
RegisterTokenProvider(providerName, provider);
|
||||
|
|
@ -122,6 +122,21 @@ namespace Microsoft.AspNetCore.Identity
|
|||
|
||||
internal IdentityOptions Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag indicating whether the backing user store supports authentication tokens.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// true if the backing user store supports authentication tokens, otherwise false.
|
||||
/// </value>
|
||||
public virtual bool SupportsUserAuthenticationTokens
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Store is IUserAuthenticationTokenStore<TUser>;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag indicating whether the backing user store supports two factor authentication.
|
||||
/// </summary>
|
||||
|
|
@ -1601,7 +1616,7 @@ namespace Microsoft.AspNetCore.Identity
|
|||
/// </summary>
|
||||
/// <param name="providerName">The name of the provider to register.</param>
|
||||
/// <param name="provider">The provider to register.</param>
|
||||
public virtual void RegisterTokenProvider(string providerName, IUserTokenProvider<TUser> provider)
|
||||
public virtual void RegisterTokenProvider(string providerName, IUserTwoFactorTokenProvider<TUser> provider)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (provider == null)
|
||||
|
|
@ -1953,6 +1968,92 @@ namespace Microsoft.AspNetCore.Identity
|
|||
return store.GetUsersInRoleAsync(NormalizeKey(roleName), CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an authentication token for a user.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="loginProvider">The authentication scheme for the provider the token is associated with.</param>
|
||||
/// <param name="tokenName">The name of the token.</param>
|
||||
/// <returns></returns>
|
||||
public virtual Task<string> GetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var store = GetTokenStore();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
if (loginProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loginProvider));
|
||||
}
|
||||
if (tokenName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tokenName));
|
||||
}
|
||||
|
||||
return store.GetTokenAsync(user, loginProvider, tokenName, CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an authentication token for a user.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="loginProvider">The authentication scheme for the provider the token is associated with.</param>
|
||||
/// <param name="tokenName">The name of the token.</param>
|
||||
/// <param name="tokenValue">The value of the token.</param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<IdentityResult> SetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName, string tokenValue)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var store = GetTokenStore();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
if (loginProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loginProvider));
|
||||
}
|
||||
if (tokenName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tokenName));
|
||||
}
|
||||
|
||||
// REVIEW: should updating any tokens affect the security stamp?
|
||||
await store.SetTokenAsync(user, loginProvider, tokenName, tokenValue, CancellationToken);
|
||||
return await UpdateUserAsync(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove an authentication token for a user.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="loginProvider">The authentication scheme for the provider the token is associated with.</param>
|
||||
/// <param name="tokenName">The name of the token.</param>
|
||||
/// <returns>Whether a token was removed.</returns>
|
||||
public virtual async Task<IdentityResult> RemoveAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var store = GetTokenStore();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
if (loginProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loginProvider));
|
||||
}
|
||||
if (tokenName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tokenName));
|
||||
}
|
||||
|
||||
await store.RemoveTokenAsync(user, loginProvider, tokenName, CancellationToken);
|
||||
return await UpdateUserAsync(user);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the role manager and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
|
|
@ -1966,7 +2067,6 @@ namespace Microsoft.AspNetCore.Identity
|
|||
}
|
||||
}
|
||||
|
||||
// IUserFactorStore methods
|
||||
internal IUserTwoFactorStore<TUser> GetUserTwoFactorStore()
|
||||
{
|
||||
var cast = Store as IUserTwoFactorStore<TUser>;
|
||||
|
|
@ -1977,7 +2077,6 @@ namespace Microsoft.AspNetCore.Identity
|
|||
return cast;
|
||||
}
|
||||
|
||||
// IUserLockoutStore methods
|
||||
internal IUserLockoutStore<TUser> GetUserLockoutStore()
|
||||
{
|
||||
var cast = Store as IUserLockoutStore<TUser>;
|
||||
|
|
@ -1988,7 +2087,6 @@ namespace Microsoft.AspNetCore.Identity
|
|||
return cast;
|
||||
}
|
||||
|
||||
// IUserEmailStore methods
|
||||
internal IUserEmailStore<TUser> GetEmailStore(bool throwOnFail = true)
|
||||
{
|
||||
var cast = Store as IUserEmailStore<TUser>;
|
||||
|
|
@ -1999,7 +2097,6 @@ namespace Microsoft.AspNetCore.Identity
|
|||
return cast;
|
||||
}
|
||||
|
||||
// IUserPhoneNumberStore methods
|
||||
internal IUserPhoneNumberStore<TUser> GetPhoneNumberStore()
|
||||
{
|
||||
var cast = Store as IUserPhoneNumberStore<TUser>;
|
||||
|
|
@ -2010,7 +2107,6 @@ namespace Microsoft.AspNetCore.Identity
|
|||
return cast;
|
||||
}
|
||||
|
||||
// Two factor APIS
|
||||
internal async Task<byte[]> CreateSecurityTokenAsync(TUser user)
|
||||
{
|
||||
return Encoding.Unicode.GetBytes(await GetSecurityStampAsync(user));
|
||||
|
|
@ -2137,11 +2233,6 @@ namespace Microsoft.AspNetCore.Identity
|
|||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate user and update. Called by other UserManager methods
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<IdentityResult> UpdateUserAsync(TUser user)
|
||||
{
|
||||
var result = await ValidateUserInternal(user);
|
||||
|
|
@ -2154,7 +2245,16 @@ namespace Microsoft.AspNetCore.Identity
|
|||
return await Store.UpdateAsync(user, CancellationToken);
|
||||
}
|
||||
|
||||
// IUserPasswordStore methods
|
||||
private IUserAuthenticationTokenStore<TUser> GetTokenStore()
|
||||
{
|
||||
var cast = Store as IUserAuthenticationTokenStore<TUser>;
|
||||
if (cast == null)
|
||||
{
|
||||
throw new NotSupportedException("Resources.StoreNotIUserAuthenticationTokenStore");
|
||||
}
|
||||
return cast;
|
||||
}
|
||||
|
||||
private IUserPasswordStore<TUser> GetPasswordStore()
|
||||
{
|
||||
var cast = Store as IUserPasswordStore<TUser>;
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
|
|||
Assert.True(VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId"));
|
||||
Assert.True(VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"));
|
||||
Assert.True(VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"));
|
||||
Assert.True(VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value"));
|
||||
|
||||
VerifyIndex(db, "AspNetRoles", "RoleNameIndex");
|
||||
VerifyIndex(db, "AspNetUsers", "UserNameIndex");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder.Internal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity.Test;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.InMemory.Test
|
||||
{
|
||||
public class ControllerTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task VerifyAccountControllerSignIn(bool isPersistent)
|
||||
{
|
||||
var context = new Mock<HttpContext>();
|
||||
var auth = new Mock<AuthenticationManager>();
|
||||
context.Setup(c => c.Authentication).Returns(auth.Object).Verifiable();
|
||||
auth.Setup(a => a.SignInAsync(new IdentityCookieOptions().ApplicationCookieAuthenticationScheme,
|
||||
It.IsAny<ClaimsPrincipal>(),
|
||||
It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
|
||||
// REVIEW: is persistant mocking broken
|
||||
//It.Is<AuthenticationProperties>(v => v.IsPersistent == isPersistent))).Returns(Task.FromResult(0)).Verifiable();
|
||||
var contextAccessor = new Mock<IHttpContextAccessor>();
|
||||
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddSingleton(contextAccessor.Object);
|
||||
services.AddIdentity<TestUser, TestRole>();
|
||||
services.AddSingleton<IUserStore<TestUser>, InMemoryStore<TestUser, TestRole>>();
|
||||
services.AddSingleton<IRoleStore<TestRole>, InMemoryStore<TestUser, TestRole>>();
|
||||
|
||||
var app = new ApplicationBuilder(services.BuildServiceProvider());
|
||||
app.UseCookieAuthentication();
|
||||
|
||||
// Act
|
||||
var user = new TestUser
|
||||
{
|
||||
UserName = "Yolo"
|
||||
};
|
||||
const string password = "Yol0Sw@g!";
|
||||
var userManager = app.ApplicationServices.GetRequiredService<UserManager<TestUser>>();
|
||||
var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<TestUser>>();
|
||||
|
||||
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password));
|
||||
|
||||
var result = await signInManager.PasswordSignInAsync(user, password, isPersistent, false);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Succeeded);
|
||||
context.VerifyAll();
|
||||
auth.VerifyAll();
|
||||
contextAccessor.VerifyAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VerifyAccountControllerExternalLoginWithTokensFlow()
|
||||
{
|
||||
// Setup the external cookie like it would look from a real OAuth2
|
||||
var externalId = "<externalId>";
|
||||
var authScheme = "<authScheme>";
|
||||
var externalIdentity = new ClaimsIdentity();
|
||||
externalIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, externalId));
|
||||
var externalPrincipal = new ClaimsPrincipal(externalIdentity);
|
||||
var externalLogin = new ExternalLoginInfo(externalPrincipal, authScheme, externalId, "displayname")
|
||||
{
|
||||
AuthenticationTokens = new[] {
|
||||
new AuthenticationToken { Name = "refresh_token", Value = "refresh" },
|
||||
new AuthenticationToken { Name = "access_token", Value = "access" }
|
||||
}
|
||||
};
|
||||
|
||||
var auth = new Mock<AuthenticationManager>();
|
||||
auth.Setup(a => a.AuthenticateAsync(It.IsAny<AuthenticateContext>())).Returns(Task.FromResult(0));
|
||||
var context = new Mock<HttpContext>();
|
||||
context.Setup(c => c.Authentication).Returns(auth.Object).Verifiable();
|
||||
var contextAccessor = new Mock<IHttpContextAccessor>();
|
||||
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddSingleton(contextAccessor.Object);
|
||||
services.AddIdentity<TestUser, TestRole>();
|
||||
services.AddSingleton<IUserStore<TestUser>, InMemoryStore<TestUser, TestRole>>();
|
||||
services.AddSingleton<IRoleStore<TestRole>, InMemoryStore<TestUser, TestRole>>();
|
||||
|
||||
var app = new ApplicationBuilder(services.BuildServiceProvider());
|
||||
app.UseCookieAuthentication();
|
||||
|
||||
// Act
|
||||
var user = new TestUser
|
||||
{
|
||||
UserName = "Yolo"
|
||||
};
|
||||
var userManager = app.ApplicationServices.GetRequiredService<UserManager<TestUser>>();
|
||||
var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<TestUser>>();
|
||||
|
||||
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user));
|
||||
IdentityResultAssert.IsSuccess(await userManager.AddLoginAsync(user, new UserLoginInfo(authScheme, externalId, "whatever")));
|
||||
IdentityResultAssert.IsSuccess(await signInManager.UpdateExternalAuthenticationTokensAsync(externalLogin));
|
||||
Assert.Equal("refresh", await userManager.GetAuthenticationTokenAsync(user, authScheme, "refresh_token"));
|
||||
Assert.Equal("access", await userManager.GetAuthenticationTokenAsync(user, authScheme, "access_token"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder.Internal;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Identity.Test;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.InMemory.Test
|
||||
{
|
||||
public class HttpSignInTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task VerifyAccountControllerSignIn(bool isPersistent)
|
||||
{
|
||||
var context = new Mock<HttpContext>();
|
||||
var auth = new Mock<AuthenticationManager>();
|
||||
context.Setup(c => c.Authentication).Returns(auth.Object).Verifiable();
|
||||
auth.Setup(a => a.SignInAsync(new IdentityCookieOptions().ApplicationCookieAuthenticationScheme,
|
||||
It.IsAny<ClaimsPrincipal>(),
|
||||
It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
|
||||
// REVIEW: is persistant mocking broken
|
||||
//It.Is<AuthenticationProperties>(v => v.IsPersistent == isPersistent))).Returns(Task.FromResult(0)).Verifiable();
|
||||
var contextAccessor = new Mock<IHttpContextAccessor>();
|
||||
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddSingleton(contextAccessor.Object);
|
||||
services.AddIdentity<TestUser, TestRole>();
|
||||
services.AddSingleton<IUserStore<TestUser>, InMemoryStore<TestUser, TestRole>>();
|
||||
services.AddSingleton<IRoleStore<TestRole>, InMemoryStore<TestUser, TestRole>>();
|
||||
|
||||
var app = new ApplicationBuilder(services.BuildServiceProvider());
|
||||
app.UseCookieAuthentication();
|
||||
|
||||
// Act
|
||||
var user = new TestUser
|
||||
{
|
||||
UserName = "Yolo"
|
||||
};
|
||||
const string password = "Yol0Sw@g!";
|
||||
var userManager = app.ApplicationServices.GetRequiredService<UserManager<TestUser>>();
|
||||
var signInManager = app.ApplicationServices.GetRequiredService<SignInManager<TestUser>>();
|
||||
|
||||
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password));
|
||||
|
||||
var result = await signInManager.PasswordSignInAsync(user, password, isPersistent, false);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Succeeded);
|
||||
context.VerifyAll();
|
||||
auth.VerifyAll();
|
||||
contextAccessor.VerifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,8 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
|||
IQueryableUserStore<TUser>,
|
||||
IUserTwoFactorStore<TUser>,
|
||||
IQueryableRoleStore<TRole>,
|
||||
IRoleClaimStore<TRole>
|
||||
IRoleClaimStore<TRole>,
|
||||
IUserAuthenticationTokenStore<TUser>
|
||||
where TRole : TestRole
|
||||
where TUser : TestUser
|
||||
{
|
||||
|
|
@ -499,6 +500,54 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
|||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
|
||||
{
|
||||
var tokenEntity =
|
||||
user.Tokens.SingleOrDefault(
|
||||
l =>
|
||||
l.TokenName == name && l.LoginProvider == loginProvider &&
|
||||
l.UserId == user.Id);
|
||||
if (tokenEntity != null)
|
||||
{
|
||||
tokenEntity.TokenValue = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.Tokens.Add(new TestUserToken
|
||||
{
|
||||
UserId = user.Id,
|
||||
LoginProvider = loginProvider,
|
||||
TokenName = name,
|
||||
TokenValue = value
|
||||
});
|
||||
}
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var tokenEntity =
|
||||
user.Tokens.SingleOrDefault(
|
||||
l =>
|
||||
l.TokenName == name && l.LoginProvider == loginProvider &&
|
||||
l.UserId == user.Id);
|
||||
if (tokenEntity != null)
|
||||
{
|
||||
user.Tokens.Remove(tokenEntity);
|
||||
}
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<string> GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var tokenEntity =
|
||||
user.Tokens.SingleOrDefault(
|
||||
l =>
|
||||
l.TokenName == name && l.LoginProvider == loginProvider &&
|
||||
l.UserId == user.Id);
|
||||
return Task.FromResult(tokenEntity?.TokenValue);
|
||||
}
|
||||
|
||||
public IQueryable<TRole> Roles
|
||||
{
|
||||
get { return _roles.Values.AsQueryable(); }
|
||||
|
|
|
|||
|
|
@ -1133,7 +1133,7 @@ namespace Microsoft.AspNetCore.Identity.Test
|
|||
}
|
||||
}
|
||||
|
||||
private class NoOpTokenProvider : IUserTokenProvider<TestUser>
|
||||
private class NoOpTokenProvider : IUserTwoFactorTokenProvider<TestUser>
|
||||
{
|
||||
public string Name { get; } = "Noop";
|
||||
|
||||
|
|
|
|||
|
|
@ -89,17 +89,9 @@ namespace Microsoft.AspNetCore.Identity.Test
|
|||
/// </summary>
|
||||
public virtual int AccessFailedCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Navigation property for users in the role
|
||||
/// </summary>
|
||||
public virtual ICollection<TestUserRole<TKey>> Roles { get; private set; } = new List<TestUserRole<TKey>>();
|
||||
/// <summary>
|
||||
/// Navigation property for users claims
|
||||
/// </summary>
|
||||
public virtual ICollection<TestUserClaim<TKey>> Claims { get; private set; } = new List<TestUserClaim<TKey>>();
|
||||
/// <summary>
|
||||
/// Navigation property for users logins
|
||||
/// </summary>
|
||||
public virtual ICollection<TestUserLogin<TKey>> Logins { get; private set; } = new List<TestUserLogin<TKey>>();
|
||||
public virtual ICollection<TestUserToken<TKey>> Tokens { get; private set; } = new List<TestUserToken<TKey>>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.Test
|
||||
{
|
||||
public class TestUserToken : TestUserToken<string> { }
|
||||
|
||||
/// <summary>
|
||||
/// Entity type for a user's login (i.e. facebook, google)
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
public class TestUserToken<TKey> where TKey : IEquatable<TKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// The login provider for the login (i.e. facebook, google)
|
||||
/// </summary>
|
||||
public virtual string LoginProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key representing the login for the provider
|
||||
/// </summary>
|
||||
public virtual string TokenName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the login
|
||||
/// </summary>
|
||||
public virtual string TokenValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User Id for the user who owns this login
|
||||
/// </summary>
|
||||
public virtual TKey UserId { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -815,7 +815,7 @@ namespace Microsoft.AspNetCore.Identity.Test
|
|||
Assert.False(await manager.IsEmailConfirmedAsync(user));
|
||||
}
|
||||
|
||||
private class StaticTokenProvider : IUserTokenProvider<TUser>
|
||||
private class StaticTokenProvider : IUserTwoFactorTokenProvider<TUser>
|
||||
{
|
||||
public async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
|
||||
{
|
||||
|
|
@ -1909,6 +1909,30 @@ namespace Microsoft.AspNetCore.Identity.Test
|
|||
Assert.True(!factors.Any());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanGetSetUpdateAndRemoveUserToken()
|
||||
{
|
||||
if (ShouldSkipDbTests())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var manager = CreateManager();
|
||||
var user = CreateTestUser();
|
||||
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
|
||||
Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||
IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value"));
|
||||
Assert.Equal("value", await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||
|
||||
IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value2"));
|
||||
Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||
|
||||
IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "whatevs", "name"));
|
||||
Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||
|
||||
IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "provider", "name"));
|
||||
Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanGetValidTwoFactor()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue