31

I need to secure my web-token with signing and encryption. I wrote the next lines of code:

var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
      Subject = new ClaimsIdentity(new[]
         {
             new Claim(ClaimTypes.Name, owner.Name),
             new Claim(ClaimTypes.Role, owner.RoleClaimType),
             new Claim("custom claim type", "custom content")
         }),
      TokenIssuerName = "self",
      AppliesToAddress = "http://www.example.com",
      Lifetime = new Lifetime(now, now.AddSeconds(60 * 3)),
      EncryptingCredentials = new X509EncryptingCredentials(new X509Certificate2(cert)),
      SigningCredentials = new X509SigningCredentials(cert1)
};
var token = (JwtSecurityToken)tokenHandler.CreateToken(tokenDescriptor);            
var tokenString = tokenHandler.WriteToken(token);

So, I am using some certificates, generated with makecert.exe. Then I read token string with another JwtSecurityTokenHandler:

var tokenHandlerDecr = new JwtSecurityTokenHandler();
var tok = tokenHandlerDecr.ReadToken(tokenString);

And token content is not encrypted (I can see json in tok variable under debugger). What am I doing wrong? How to encrypt token data?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Qué Padre
  • 2,005
  • 3
  • 24
  • 39

4 Answers4

55

I know this an old post, but I am adding my answer in case if someone is still searching for the answer.

This issue is addressed in Microsoft.IdentityModel.Tokens version 5.1.3. There is an overloaded method available in the CreateJwtSecurityToken function which accepts the encrypting credentials to encrypt the token.

If the receiver does not validate the signature and tries to read JWT as is then the claims are empty. Following is the code snippet:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

const string sec = "ProEMLh5e_qnzdNUQrqdHPgp";
const string sec1 = "ProEMLh5e_qnzdNU";
var securityKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(sec));
var securityKey1 = new SymmetricSecurityKey(Encoding.Default.GetBytes(sec1)); 

var signingCredentials = new SigningCredentials(
    securityKey,
    SecurityAlgorithms.HmacSha512);

List<Claim> claims = new List<Claim>()
{
    new Claim("sub", "test"),
};

var ep = new EncryptingCredentials(
    securityKey1,
    SecurityAlgorithms.Aes128KW,
    SecurityAlgorithms.Aes128CbcHmacSha256);

var handler = new JwtSecurityTokenHandler();

var jwtSecurityToken = handler.CreateJwtSecurityToken(
    "issuer",
    "Audience",
    new ClaimsIdentity(claims),
    DateTime.Now,
    DateTime.Now.AddHours(1),
    DateTime.Now,
    signingCredentials,
    ep);


string tokenString = handler.WriteToken(jwtSecurityToken);

// Id someone tries to view the JWT without validating/decrypting the token,
// then no claims are retrieved and the token is safe guarded.
var jwt = new JwtSecurityToken(tokenString);

And here is the code to validate/decrypt the token:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

const string sec = "ProEMLh5e_qnzdNUQrqdHPgp";
const string sec1 = "ProEMLh5e_qnzdNU";
var securityKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(sec));
var securityKey1 = new SymmetricSecurityKey(Encoding.Default.GetBytes(sec1));

// This is the input JWT which we want to validate.
string tokenString = string.Empty;

// If we retrieve the token without decrypting the claims, we won't get any claims
// DO not use this jwt variable
var jwt = new JwtSecurityToken(tokenString);

// Verification
var tokenValidationParameters = new TokenValidationParameters()
{
    ValidAudiences = new string[]
    {
        "536481524875-glk7nibpj1q9c4184d4n3gittrt8q3mn.apps.googleusercontent.com"
    },
    ValidIssuers = new string[]
    {
        "https://accounts.google.com"
    },
    IssuerSigningKey = securityKey,
    // This is the decryption key
    TokenDecryptionKey = securityKey1
};

SecurityToken validatedToken;
var handler = new JwtSecurityTokenHandler();

handler.ValidateToken(tokenString, tokenValidationParameters, out validatedToken);
Franklin Yu
  • 8,920
  • 6
  • 43
  • 57
Amey
  • 1,216
  • 18
  • 28
  • how would i decrypt the token since there is no option of passing the key when validating the token? – Sang Suantak May 30 '17 at 05:28
  • 1
    @SangSuantak TokenValidationParameters has TokenDecryptionKey property. Validator internally uses this property to decrypt the token. I have updated my answer to include the decryption part – Amey May 30 '17 at 11:41
  • 1
    @Amey can we use public key private key pair to encrypt and decrypt the JWT token. If yes, how. – Rajat Agrawal Mar 14 '18 at 12:55
  • [`Microsoft.IdentityModel` is deprecated](https://learn.microsoft.com/en-us/dotnet/framework/security/wif-api-reference). Do you have an up-to-date example? – Jacob Stamm May 11 '18 at 18:24
  • @JacobStamm How does that page say that `IdentityModel` is deprecated? Only WCF is deprecated. WIF is not. – Franklin Yu Aug 31 '18 at 15:16
  • [Their GitHub repository](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) has more information. – Franklin Yu Aug 31 '18 at 15:51
  • @Amey I assume this does sign-then-encrypt when creating tokens. Is there a way to encrypt-then-sign? – Steven Liekens Oct 12 '18 at 12:13
  • @JacobStamm The current work for IdentityModel is here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet . We have several libraries that support asp.net core, you can find them on nuget.org. – Brent Schmaltz May 16 '19 at 04:25
  • @StevenLiekens the concept of sign-then-encrypt or encrypt-then-sign is not critical for JWE's. The reason is only authenticated encryption algorithms are used. What occurs with JWE's, is that first a JWS is created specifying SigningCredentials, then the JWE is created by wrapping that JWS. – Brent Schmaltz May 16 '19 at 04:28
  • @RajatAgrawal yes you can use Pub/Pri key pairs. I you use RSA keys then the libraries will create symmetricKey and wrap the key. – Brent Schmaltz May 16 '19 at 04:39
  • @Amey This procedure outputs the tokwn with `"alg": "A128KW", "enc": "A128CBC-HS256"` as the header claims. The "enc" parameter looks as expected, but shouldn't "alg" remain "HS256"? – Beltway Sep 23 '21 at 14:12
15

Try the following example

Updated Jul-2019: .NET Core, Asp.net Core

1.Create JWT

private string CreateJwt(string sub, string jti, string issuer, string audience)
{
    var claims = new[]
    {
        new Claim(JwtRegisteredClaimNames.Sub, sub),
        new Claim(JwtRegisteredClaimNames.Jti, jti),
    };

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecretKeySecretKeySecretKeySecretKeySecretKeySecretKeySecretKeyS"));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    var encryptingCredentials = new EncryptingCredentials(key, JwtConstants.DirectKeyUseAlg, SecurityAlgorithms.Aes256CbcHmacSha512);

    var jwtSecurityToken = new JwtSecurityTokenHandler().CreateJwtSecurityToken(
        issuer,
        audience,
        new ClaimsIdentity(claims),
        null,
        expires: DateTime.UtcNow.AddMinutes(5),
        null,
        signingCredentials: creds,
        encryptingCredentials: encryptingCredentials
        );
    var encryptedJWT = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);

    return encryptedJWT;
}

2.Add to ConfigureServices(IServiceCollection services) in Startup.cs

    services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,

        ValidIssuer = (string)Configuration.GetSection("JwtToken").GetValue(typeof(string), "Issuer"),
        ValidAudience = (string)Configuration.GetSection("JwtToken").GetValue(typeof(string), "Audience"),
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecretKeySecretKeySecretKeySecretKeySecretKeySecretKeySecretKeyS")),
        TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecretKeySecretKeySecretKeySecretKeySecretKeySecretKeySecretKeyS")),
        ClockSkew = TimeSpan.FromMinutes(0),
    };
});
Hung Quach
  • 2,079
  • 2
  • 17
  • 28
  • Can you analyze and help me the code to receive the Token from the request then decode and compare with the Data token stored? This code runs fine but I don't understand how it works, Thank you! – Trang Đỗ Sep 19 '19 at 10:38
8

My understanding is that Microsoft's JWT implementation doesn't currently support encryption (only signing).

Brock Allen
  • 7,385
  • 19
  • 24
2

Hung Quach's answer worked just fine for .NET 5 on Blazor for me. Although I changed it a bit to for my style.

   var claims = new List<Claim>()
            {
                new Claim(ClaimTypes.Name,userRequestDTO.Email)
            };
            var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_APISettings.SecretKey));
            var signingCredentials = new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);
            var encryptionCredentials = new EncryptingCredentials(secret, JwtConstants.DirectKeyUseAlg, SecurityAlgorithms.Aes256CbcHmacSha512);
            var tokenOptions = new JwtSecurityTokenHandler().CreateJwtSecurityToken(new SecurityTokenDescriptor()
            {
                Audience= _APISettings.ValidAudience,
                Issuer= _APISettings.ValidIssuer,
                Subject= new ClaimsIdentity(claims),
                Expires= DateTime.Now.AddMinutes(10),
                EncryptingCredentials= encryptionCredentials,
                SigningCredentials = signingCredentials
            });

            return new JwtSecurityTokenHandler().WriteToken(tokenOptions);

then I am reading the token as follows :

 JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
                var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_APISettings.SecretKey));
                var signingCredentials = new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);

                tokenHandler.ValidateToken(tokenOTP,new TokenValidationParameters
                {
                    ValidIssuer = _APISettings.ValidIssuer,
                    ValidAudience = _APISettings.ValidAudience,
                    ValidateIssuerSigningKey=true,
                    ValidateLifetime = true,
                    RequireExpirationTime = true,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    IssuerSigningKey= secret,
                    TokenDecryptionKey=secret,
                    ClockSkew = TimeSpan.Zero
                },out SecurityToken validatedToken);
                var jwtToken = (JwtSecurityToken)validatedToken;
MrSpyretos
  • 51
  • 1
  • 2