.Net Core 2.0 Web API using JWT - Adding Identity breaks the JWT authentication
I eventually put together the solution, so on the suggestion of user alwayslearning I've edited my post and I'm putting this in as an actual answer.
ok, This can be done properly. First, you need to use the Authentication options I pointed out in my edit above - that's fine.
Then you need to useservices.AddIdentityCore<TUser>()
rather than services.AddIdentity<TUser>()
. This however, doesn't add a whole bunch of things for role management, and is apparently lacking the proper constructor to give it the type of Role you want to use. This means that in my case I had to do this:
IdentityBuilder builder = services.AddIdentityCore<IdentityUser>(opt =>
{
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 8;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
}
);
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder
.AddEntityFrameworkStores<MyContext>();
//.AddDefaultTokenProviders();
builder.AddRoleValidator<RoleValidator<IdentityRole>>();
builder.AddRoleManager<RoleManager<IdentityRole>>();
builder.AddSignInManager<SignInManager<IdentityUser>>();
Having done that, the next thing is to make sure that when validating a user login (prior to sending a token), you make sure to use the SignInManager method CheckPasswordSignInAsync
and not PasswordSignInAsync
:
public async Task<IdentityUser> GetUserForLogin(string userName, string password)
{
//find user first...
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return null;
}
//validate password...
var signInResult = await _signInManager.CheckPasswordSignInAsync(user, password, false);
//if password was ok, return this user.
if (signInResult.Succeeded)
{
return user;
}
return null;
}
if you use the PasswordSignInAsync
method then you'll get a runtime error re. No IAuthenticationSignInHandler being configured.
I hope this helps someone at some point.
I have extracted the AddIdentity
code from github and created an extension method based on it that doesn't add the default Cookie Authenticator, It's now pretty similar to the built in AddIdentityCore
but can accept IdentityRole
.
/// <summary>
/// Contains extension methods to <see cref="IServiceCollection"/> for configuring identity services.
/// </summary>
public static class IdentityServiceExtensions
{
/// <summary>
/// Adds the default identity system configuration for the specified User and Role types. (Without Authentication Scheme)
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services)
where TUser : class
where TRole : class
=> services.AddIdentityWithoutAuthenticator<TUser, TRole>(setupAction: null);
/// <summary>
/// Adds and configures the identity system for the specified User and Role types. (Without Authentication Scheme)
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">An action to configure the <see cref="IdentityOptions"/>.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupAction)
where TUser : class
where TRole : class
{
// Hosting doesn't add IHttpContextAccessor by default
services.AddHttpContextAccessor();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
services.TryAddScoped<UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
}
}
Now you can use the above code normally from a WebApi project like so
.AddIdentityWithoutAuthenticator<User, IdentityRole>()