Recently I've built a service at my work to generate tokens with JWT (JSON Web Token) "protocol", I would like to show you the code and to get comments from you if that's good enough and if there are things to improve.
The service contains 2 folders and 4 classes.
- Models Folder
- IAuthContainerModel (Interface)
- JWTContainerModel (Implementation)
- Managers Folder
- IAuthService (Interface)
- JWTService (Implementation)
- IAuthContainerModel
Code:
using System.Security.Claims;
namespace AuthenticationService.Models
{
public interface IAuthContainerModel
{
#region Members
string SecretKey { get; set; }
string SecurityAlgorithm { get; set; }
Claim[] Claims { get; set; }
int ExpireMinutes { get; set; }
#endregion
}
}
- JWTContainerModel
Code:
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
namespace AuthenticationService.Models
{
public class JWTContainerModel : IAuthContainerModel
{
#region Public Methods
public int ExpireMinutes { get; set; } = 10080; // 7 days.
public string SecretKey { get; set; } = "TW9zaGVFcmV6UHJpdmF0ZUtleQ=="; // This secret key should be moved to some configurations outter server.
public string SecurityAlgorithm { get; set; } = SecurityAlgorithms.HmacSha256Signature;
public Claim[] Claims { get; set; }
#endregion
}
}
ExpireMinutes is a default value and SecretKey is also a default value, of course the secret key will be in the configurations section inside the server
- IAuthService
Code:
using System.Security.Claims;
using System.Collections.Generic;
using AuthenticationService.Models;
namespace AuthenticationService.Managers
{
public interface IAuthService
{
string SecretKey { get; set; }
bool IsTokenValid(string token);
string GenerateToken(IAuthContainerModel model);
IEnumerable<Claim> GetTokenClaims(string token);
}
}
- JWTService
Code:
using System;
using System.Security.Claims;
using System.Collections.Generic;
using AuthenticationService.Models;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
namespace AuthenticationService.Managers
{
public class JWTService : IAuthService
{
#region Members
/// <summary>
/// The secret key we use to encrypt out token with.
/// </summary>
public string SecretKey { get; set; }
#endregion
#region Constructor
public JWTService(string secretKey)
{
SecretKey = secretKey;
}
#endregion
#region Public Methods
/// <summary>
/// Validates whether a given token is valid or not, and returns true in case the token is valid otherwise it will return false;
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public bool IsTokenValid(string token)
{
if (string.IsNullOrEmpty(token))
throw new ArgumentException("Given token is null or empty.");
TokenValidationParameters tokenValidationParameters = GetTokenValidationParameters();
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
try
{
ClaimsPrincipal tokenValid = jwtSecurityTokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken validatedToken);
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Generates token by given model.
/// Validates whether the given model is valid, then gets the symmetric key.
/// Encrypt the token and returns it.
/// </summary>
/// <param name="model"></param>
/// <returns>Generated token.</returns>
public string GenerateToken(IAuthContainerModel model)
{
if (model == null || model.Claims == null || model.Claims.Length == 0)
throw new ArgumentException("Arguments to create token are not valid.");
SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(model.Claims),
Expires = DateTime.UtcNow.AddMinutes(Convert.ToInt32(model.ExpireMinutes)),
SigningCredentials = new SigningCredentials(GetSymmetricSecurityKey(), model.SecurityAlgorithm)
};
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
string token = jwtSecurityTokenHandler.WriteToken(securityToken);
return token;
}
/// <summary>
/// Receives the claims of token by given token as string.
/// </summary>
/// <remarks>
/// Pay attention, one the token is FAKE the method will throw an exception.
/// </remarks>
/// <param name="token"></param>
/// <returns>IEnumerable of claims for the given token.</returns>
public IEnumerable<Claim> GetTokenClaims(string token)
{
if (string.IsNullOrEmpty(token))
throw new ArgumentException("Given token is null or empty.");
TokenValidationParameters tokenValidationParameters = GetTokenValidationParameters();
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
try
{
ClaimsPrincipal tokenValid = jwtSecurityTokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken validatedToken);
return tokenValid.Claims;
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
#region Private Methods
private SecurityKey GetSymmetricSecurityKey()
{
byte[] symmetricKey = Convert.FromBase64String(SecretKey);
return new SymmetricSecurityKey(symmetricKey);
}
private TokenValidationParameters GetTokenValidationParameters()
{
return new TokenValidationParameters()
{
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = GetSymmetricSecurityKey()
};
}
#endregion
}
}
What do you say about this code? is it implemented correctly? would you fix it in some way? I will be happy to hear your opinions!
1 Answer 1
It looks fine to me, I just a few minor suggestions.
catch (Exception ex) { throw ex; }
You probably left this in by accident, but you're better off without a catch block if all you're going to do is re-throw. And if you are going to keep this block, throw;
is always preferable to throw ex;
I question the wisdom of having a "default" key specified in the code. It seems to me that it should always read from outside and complain loudly if it fails. With a usable default key, it opens the possibility that it could use the wrong key without you knowing about it.
Explore related questions
See similar questions with these tags.