7
\$\begingroup\$

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.

  1. Models Folder
    • IAuthContainerModel (Interface)
    • JWTContainerModel (Implementation)
  2. 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!

t3chb0t
44.6k9 gold badges84 silver badges190 bronze badges
asked Sep 16, 2018 at 6:53
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

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.

t3chb0t
44.6k9 gold badges84 silver badges190 bronze badges
answered Dec 15, 2018 at 23:32
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.