I have an asp.net core application using json web tokens for authentication, this worked fine when my user Id was a string, but stopped working after changing to an int.
My IdentityUser type was originally this
public class AppUser : IdentityUser
{
//other properties...
}
I updated this;
public class AppUser : IdentityUser<int>
{
//other properties...
}
I changed my Startup configuration from this;
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtIssuerOptions.Issuer;
options.Audience = jwtIssuerOptions.Audience;
options.SigningCredentials = jwtIssuerOptions.SigningCredentials;
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtIssuerOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtIssuerOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtIssuerOptions.Issuer;
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// add identity
var builder = services.AddIdentityCore<AppUser>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
o.SignIn.RequireConfirmedEmail = true;
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
To this;
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtIssuerOptions.Issuer;
options.Audience = jwtIssuerOptions.Audience;
options.SigningCredentials = jwtIssuerOptions.SigningCredentials;
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtIssuerOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtIssuerOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtIssuerOptions.Issuer;
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
});
// add identity
var builder = services.AddIdentity<AppUser, IdentityRole<int>>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
o.SignIn.RequireConfirmedEmail = true;
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole<int>), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
I use a the following method to generate the token, this is unchanged between implementations.
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst(JwtClaimIdentifiers.Rol),
identity.FindFirst(JwtClaimIdentifiers.Id)
};
// Create the JWT security token and encode it.
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
And finally this method is used to create the claims identity;
public ClaimsIdentity GenerateClaimsIdentity(string userName, int id)
{
return new ClaimsIdentity(new GenericIdentity(userName, "Token"), new[]
{
new Claim(JwtClaimIdentifiers.Id, id.ToString(), ClaimValueTypes.Integer32),
});
}
User.Identity.IsAuthenticated is now always false when validating and the ClaimsIdentity appears to be empty of the information I saw before converting my AppUser from a string ID type to an int Id type.
The best theory I have so far is that the problem may be related to serialisation/deserialisation of the token values but I'm clutching at straws and have little idea how I might debug it.
What have I missed?
Taking Connor Low's advice I supplied delegates to all the JwtBearerOptions
events. None of these where hit, indicating that my setup was insufficient and that the authentication scheme I had configured was not being used at all.
I reverted to addIdentityCore
var builder = services.AddIdentityCore<AppUser>(o =>
Although I could no longer specify that I was using IdentityRole<int>
instead of the default like this;
var builder = services.AddIdentity<AppUser, IdentityRole<int>>(o =>
This seemed to be unnecessary provided I had the following line;
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole<int>), builder.Services);
With these changes in place authentication worked and the breakpoints I had added at Connor Low's suggestion were being hit.