1

According this question, I'm using the snippet below, to sign and encrypt the JWToken.

var claims = new Claim[] { new SomeClaimes() };
var scKey = Encoding.UTF8.GetBytes("SOME KEY");
var ecKeyTemp = Encoding.UTF8.GetBytes("SOME OTHER KEY");

byte[] ecKey = new byte[256 / 8];
Array.Copy(ecKeyTemp, ecKey, 256 / 8);

var tokenDescriptor = new SecurityTokenDescriptor {
    Subject = new ClaimsIdentity(claims),
    SigningCredentials = new SigningCredentials(
        new SymmetricSecurityKey(
            scKey),
            SecurityAlgorithms.HmacSha512),
    EncryptingCredentials = new EncryptingCredentials(
        new SymmetricSecurityKey(
            ecKey),
            SecurityAlgorithms.Aes256KW,
            SecurityAlgorithms.Aes256CbcHmacSha512), 
    Issuer = "My Jwt Issuer",
    Audience = "My Jwt Audience",
    IssuedAt = DateTime.UtcNow,
    Expires = DateTime.Now.AddDays(7),
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);

And here is my service registration:

services
.AddAuthentication(o => {
    o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(cfg => {
    cfg.RequireHttpsMetadata = false;
    cfg.SaveToken = true;
    cfg.TokenValidationParameters = new TokenValidationParameters {
        ValidIssuer = "My Jwt Issuer",
        ValidAudience = "My Jwt Audience",
        IssuerSigningKey = new SymmetricSecurityKey(SameKeyAsGenerating)),
        TokenDecryptionKey = new SymmetricSecurityKey(SameKeyAsGenerating),
        ClockSkew = TimeSpan.Zero, 
        RequireSignedTokens = true,
        RequireExpirationTime = true,
        SaveSigninToken = true,
        ValidateActor = true,
        ValidateAudience = true,
        ValidateIssuer = true,
        ValidateIssuerSigningKey = true,
        ValidateLifetime = true,
        ValidateTokenReplay = true,
    };
});

The problem is that the authentication layer authenticates both encrypted and not-encrypted token as authorized. I mean when I create a token without the EncryptingCredentials (just a signed token, and not encrypted), the token is still valid and the request is authorized. The question is how to force authentication layer to only accept signed-encrypted tokens and reject just-signed-not-encrypted tokens?

UPDATE: The Solution:

Thanks to sellotape's answer, I implemented this JwtEncryptedSecurityTokenHandler:

public class JwtEncryptedSecurityTokenHandler : JwtSecurityTokenHandler {

    [DebuggerStepThrough]
    public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) {
        if (string.IsNullOrWhiteSpace(token))
            throw new ArgumentNullException(nameof (token));

        if (validationParameters == null)
            throw new ArgumentNullException(nameof (validationParameters));

        if (token.Length > MaximumTokenSizeInBytes)
            throw new ArgumentException(
                $"IDX10209: token has length: '{token.Length}' which is larger than the MaximumTokenSizeInBytes: '{MaximumTokenSizeInBytes}'.");

        var strArray = token.Split(new[] { '.' }, 6);
        if (strArray.Length == 5)
            return base.ValidateToken(token, validationParameters, out validatedToken);

        throw new SecurityTokenDecryptionFailedException();
    }
}

And used the new handler in Startup.ConfigureServices():

.AddJwtBearer(cfg => {
    cfg.RequireHttpsMetadata = false;
    // other configurations...
    cfg.SecurityTokenValidators.Clear();
    cfg.SecurityTokenValidators.Add(new JwtEncryptedSecurityTokenHandler());
});

For more explanation, see the accepted answer.

amiry jd
  • 27,021
  • 30
  • 116
  • 215

1 Answers1

1

I'm sure there are a few ways to do this but how about:

  • Implement ISecurityTokenValidator, probably inheriting from SecurityTokenHandler; the required overrides are fairly simple and could largely be copied from JwtSecurityTokenHandler. Override ValidateToken() and throw if the JWT is not encrypted**.

  • Add the new handler/validator in the AddJwtBearer() configure-options action: cfg.SecurityTokenValidators.Add(new RequireEncryptedTokenHandler());


** If you look at the source for JwtSecurityTokenHandler.ValidateToken() (not sure exactly which version you're using but I think the message is the same), it seems that "is encrypted" is simply decided as "has 5 parts to it", which should be easy to implement (copy/paste) in your new handler.

sellotape
  • 8,034
  • 2
  • 26
  • 30