How to generate and validate JWT token in 3 languages?
In this #SOreadyToHelp, I will describe the strategies to generate and validate a JWT, no matter the language
Elementary tips
- Usually and commonly, the user/password are exchanged for a new JWT
- JWT should not store anything sensible like a password. But some companies like Microsoft store not a sensible values but a lot of information https://gist.github.com/jrichardsz/6512f9b2b4c38620bf00e24c71b6cf2d
- For an enterprises, the encrypt and decrypt process should not be executed at the microservice layer. It should be performed by another server like: Auth0, okta, Keycloack, etc also called IAM, security platforms, authorization platforms, oauth2 providers, etc
- Your microservice only receives the token and send it to the security platform. The best way to do this is with a middleware on any language.
- The security platform returns a response with data in a syntax that client (c# in your case) understand if token is legit, valid, not expired, user exist, user is allowed to perform the operation, etc. I used this in some implementation in which I said: “User is allowed to perform this operation and this is its username”
- endpoint:
acme-security.com/v1/oauth2/token/validate
- request:
eyJ0eXAiOiJKV1Qi...
- response:
{
"isAllowed": true,
"subject": "jane@blindspot.com"
}
Also you can do this with an api gateway
- On a more real scenario, we need more than a simple jwt validation. We need to ensure that user admin/guest are allowed to execute specific endpoints. We don’t want that a employee invoke directly the payment endpoint set its own salary :/
- Follow the oauth2 spec if you will implement your own security platform
- Jwt should be sent as Authorization Header with value
Authorization : Bearer eyJ0eXAiOiJKV1...
IMPORTANT: If you will implement your own jwt generation and validation, use ENV variables to hide it the secret and protect it as much as you can for your production environment
JWT handling with Nodejs, Java & C# Netcore
If the previous steps are understood, you only need one of these approaches:
- Use some IAM or security platforms like auth0, okta, keycloack, etc
- A simple c# web/microservice/api (that also functions as security platform) to receive token and return if user is allowed
- A class to to the validation inside of the target microservice
JWT Generation
//NODE.JS "jsonwebtoken"
function generateToken(userId){
let jwtSecretKey = process.env.JWT_SECRET_KEY;
let data = {
time: Date(),
userId: userId,
}
return jwt.sign(data, jwtSecretKey);
}
//JAVA io.jsonwebtoken.JwtBuilder
private String generateJwtToken(String issuer, String subject, String secret, long expireInMillis) {
// The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = (new Date()).getTime();
// We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secret);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
// Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(UUID.randomUUID().toString()).setIssuedAt(now)
.setSubject(subject).setIssuer(issuer).signWith(signatureAlgorithm, signingKey);
long expMillis = nowMillis + expireInMillis;
builder.setExpiration(new Date(expMillis));
// Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
//C#
public string GenerateToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(SUPER_SECRET_KEY);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
JWT Decode or validation
//NODE.JS "jsonwebtoken"
async function validateToken(rawToken, jwtSecretKey){
//get only the value [eyJ0eXAiOiJKV1...] from: Bearer eyJ0eXAiOiJKV1...
const token = rawToken.split(" ")[1].trim();
return await jwt.verify(token, jwtSecretKey);
}
//JAVA io.jsonwebtoken.JwtBuilder
public Claims decodeJwt(String rawAuthHeader, String secret) {
//get only the value [eyJ0eXAiOiJKV1...] from: Bearer eyJ0eXAiOiJKV1...
String jwt = rawAuthHeader.split(" ")[1];
// This line will throw an exception if it is not a signed JWS (as expected)
Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(secret))
.parseClaimsJws(jwt).getBody();
return claims;
}
//c#
public int? ValidateToken(string rawAuthHeader)
{
if (rawAuthHeader == null)
return null;
//get only the value [eyJ0eXAiOiJKV1...] from: Bearer eyJ0eXAiOiJKV1...
String token = rawAuthHeader.Split(" ")[0];
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(SUPER_SECRET_KEY);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// return user id from JWT token if validation successful
return userId;
}
catch
{
// I'm not the author of this code :v
// https://jasonwatmore.com/post/2021/06/02/net-5-create-and-validate-jwt-tokens-use-custom-jwt-middleware
return null;
}
}
This code throws errors when token is expired or some buddy tries to send a jwt without your SUPER_SECRET_KEY
You could use these snippets directly in your token generation and inside of your endpoints
//Nodes "jsonwebtoken"
app.post("/employee/payment", async (req, res) => {
let jwtSecretKey = process.env.JWT_SECRET_KEY;
try {
const claims = await validateToken(req.headers["Authorization"], jwtSecretKey);
if(claims){
//performs the employee payment
//because token was validated
}else{
// Access Denied
return res.status(401).send(error);
}
} catch (error) {
// Access Denied
return res.status(401).send(error);
}
});
//JAVA
@RequestMapping(path="/employee/payment", method = RequestMethod.POST)
public ResponseEntity performPayment(@RequestHeader(HttpHeaders.Authorization) String rawAuthHeader, String foo){
Claims claims = decodeJwt(rawAuthHeader, System.getenv("JWT_SECRET"));
if(claims!=null){
return new ResponseEntity(performPayment(), HttpStatus.OK);
}
//handle the error
}
[HttpGet]
public ActionResult<List<Dictionary<string, object>>> findAllEmployees()
{
//get jwt from http received headers
validationResponse = ValidateToken(jwt)
//if validationResponse.isAllowed ....
var list = this.employeeService.findAllEmployees();
return Ok(list);
}
Or with a middleware (sometimes called filter) like a pro, to keep clean your final controllers.
Middleware
Middleware is feature in which the request must travel several steps, one by one until reach the target logic. On each step you have access to the current request and response, being able to get any value from url or body. Also you can stop the propagation to avoid the target logic will be reached in case of authorization failure.
So as you can see, you could put any logic in some middleware. Here some examples of how call local token validation. Also you can put here the token validation with some service like auth0, okta, keycloack, etc instead the direct validation
//Node.js https://expressjs.com/en/guide/using-middleware.html
// set it before the routes
app.use((req, res, next) => {
console.log('Time:', Date.now())
var rawAuthHeader = req.headers["Authorization"];
const claims = await validateToken(req.headers["Authorization"], jwtSecretKey);
if(claims){
//go to next middleware or target logic
next();
}else{
//Access Denied
//stop the propagation with a custom error response
//you could create a custom json response
return res.status(401).send(error);
}
})
@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
try {
//get authorization header from ServletRequest or inject it
HttpServletRequest httpRequest = (HttpServletRequest) req;
String rawAuthHeader = httpRequest.getHeader(headerRequestIdKey);
Claims claims = decodeJwt(rawAuthHeader, System.getenv("JWT_SECRET"));
if(claims!=null){
//go to next middleware or target logic
chain.doFilter(req, resp);
}else{
//Access Denied
//stop the propagation with a custom error response
//you could create a custom json response
HttpServletResponse response = (HttpServletResponse) resp;
response.setStatus(401);
}
} finally {
HttpServletResponse response = (HttpServletResponse) resp;
response.setStatus(401);
}
}
}
//C#
// add it in the Startup.cs
//services.AddTransient<HttpMiddlewareAuthConfigurer>();
//add these classes to have the Middleware attribute
//https://gist.github.com/jrichardsz/e23bb483794c8bc52fdd857d3f14a2bb
namespace Common.Middleware
{
[Middleware(Order = 2)]
public class HttpMiddlewareAuthConfigurer : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var rawAuthHeader = context.Request.Headers["authorization"];
var validationResponse = ValidateToken(rawAuthHeader);
if(validationResponse...){
//go to next middleware or target logic
await next(context);
}else{
//Access Denied
//stop the propagation with a custom error response
//you could create a custom json response
context.Response.StatusCode = 403;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(...));
return;
}
}
}
}
Lectures
- https://oauth.net/2/
- https://www.geeksforgeeks.org/jwt-authentication-with-node-js/
- https://www.digitalocean.com/community/tutorials/nodejs-jwt-expressjs
- https://gist.github.com/jrichardsz/6512f9b2b4c38620bf00e24c71b6cf2d
- https://www.c-sharpcorner.com/article/overview-of-middleware-in-asp-net-core/
- https://www.c-sharpcorner.com/article/asp-net-core-custom-middleware/
- https://exceptionnotfound.net/middleware-in-asp-net-6-custom-middleware-classes/
- https://stackoverflow.com/questions/55423580/how-to-set-response-status-code-in-filter-when-controller-returns-responseentity
- https://expressjs.com/en/guide/using-middleware.html
- https://dev.to/ghvstcode/understanding-express-middleware-a-beginners-guide-g73
- https://www.baeldung.com/spring-rest-http-headers
- https://www.sohamkamani.com/nodejs/jwt-authentication/
- https://www.tabnine.com/code/java/methods/io.jsonwebtoken.JwtBuilder/claim
- https://www.baeldung.com/java-json-web-tokens-jjwt
- https://metamug.com/article/security/jwt-java-tutorial-create-verify.html
- https://developer.okta.com/blog/2018/10/31/jwts-with-java
I hope this helps!