Search code examples
c#asp.net-corejwtclaims

User.Identity.IsAuthenticated always false in .net core when user id type changed from string to integer


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?


Solution

  • 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.