How to use jti claim in a JWT
Asked Answered
R

4

127

The JWT spec mentions a jti claim which allegedly can be used as a nonce to prevent replay attacks:

The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case-sensitive string. Use of this claim is OPTIONAL.

My question is, how would I go about implementing this? Do I need to store the previously used jtis and issue a new JWT with every request? If so, doesn't this defeat the purpose of JWTs? Why use a JWT instead of just storing a randomly-generated session ID in a database?

My REST API has a Mongo database and I'm not opposed to adding a Redis instance. Is there a better authentication option than JWT? I mainly just don't want to store passwords on the client which eliminates HTTP authentication as an option, however, as I'm getting deeper into this JWT stuff, I'm starting to feel as if a custom token implementation or different standard might better suit my needs. Are there any node/express packages for token based authentication that supports token revocation and rotating tokens?

Would appreciate any advice.

Remember answered 6/3, 2015 at 21:18 Comment(2)
You can do both as well. Use the JWT to send over the session's ID and perhaps some other relatively static data such as issuer, audience, etc. In fact we use a connect middleware module that uses the JTI to carry the session ID, which in turn is the key into a redis hash which stores their actual session. An approach with both would allow you to separate relatively static data in the JWT from relatively dynamic session data in redis, even when both need to be associated with their session.Churchgoer
I think I'm gonna take a similar approach, using mongo to store valid tokens and jwt for authentication. When the jwt expires, the client can request a new jwt using the token. Any pitfalls to this approach?Remember
D
101

Indeed, storing all issued JWT IDs undermines the stateless nature of using JWTs. However, the purpose of JWT IDs is to be able to revoke previously-issued JWTs. This can most easily be achieved by blacklisting instead of whitelisting. If you've included the "exp" claim (you should), then you can eventually clean up blacklisted JWTs as they expire naturally. Of course you can implement other revocation options alongside (e.g. revoke all tokens of one client based on a combination of "iat" and "aud").

Densitometer answered 29/4, 2015 at 14:26 Comment(8)
Would a combination of jti (used as a counter) and aud make sense for revoking tokens?Valentino
A disadvantage of blacklisting is that in a scalable system, before the content of the blacklist has been completely replicated to all other nodes in the cluster, the user might still be able to perform illegal actions on those nodes. Using a whitelist is more explicit and should be used.Moores
@Moores A disadvantage of whitelisting is that in a scalable system, before the update to the whitelist has been completely replicated to all other nodes in the cluster, the user might still be able to perform illegal actions on those nodes. Using a whitelist doesn't change this problem.Vandyke
@gordonmleigh No. The idea of a whitelist is that only the users on the whitelist can perform their intended actions. So before that list is replicated on the other nodes, they won't be able to perform their intended action. This may be a pain as there's more latency, but it makes the system more secure.Moores
@Moores sure, but the discussion was about revoking previously issued JWTs, which presumably started off on a whitelist, and then had to be removed to effect the revoke.Vandyke
how is this answer useful to the question asked?Forecourse
how would I be able to blacklist a token, if I never persisted them in the first place? Lets say someone's token gets stolen, but he can't provide it, and I have no mechanic to list the existing tokens of a given user, how am I going to find out and declare which token exactly must be blacklisted? It seems like I'd have to resort to a whitelist, or some list of "tokens currently in use", it that to be expected?Paederast
@c8999c3f964f64 One solution to that is to add e.g. the userId as sub (subject) in the JWT, or use that userId as a prefix to the jti. That way you can blacklist all tokens of a particular user issued before a certain date (iat).Densitometer
B
50

This is an old question, but I just worked on something similar. So I will share my thoughts here.

First of all, I agree that making database calls while validating JWT tokens undermines their primary advantage of being stateless.

None of the previous answers mention refresh tokens, but I believe they present a good trade-off between scalability and security.

In short, one can use regular auth tokens with a short expiration time (say, 15 minutes) and refresh tokens with long-lived access (say, 2 weeks). Whenever an auth token expires, the refresh token (stored more securely) is used to generate a new auth token without the user having to log in again.

The jti claim is best suited for refresh tokens. That gives you the ability to revoke access while minimizing the number of database calls made.

Let's say an average user session is 30 minutes. If you have a jti claim on regular auth tokens, then every API call is doing at least one extra database call to check if the token is not blacklisted. However, if you're only using a jti claim on refresh tokens, you will only make 2 database calls for authentication purposes during the course of a 30 minutes session (assuming each auth token expires after 15 minutes). That's a big difference.

Regarding implementation, you can use a randomly-generated UID and use it as your table's primary key. That guarantees the calls are as fast as possible. Moreover, you can add an expiration_time column with the same value as the exp claim. That way you can easily (batch) remove all the expired refresh tokens.

Why use a JWT instead of just storing a randomly-generated session ID in a database?

Or, if I may paraphrase, "why would you use a JWT refresh token rather than a random string saved on the database?"

I think you can do that, but using a JWT token has at least two advantages: (1) if the token is invalid or expired (when you decode it), you don't have to make any database calls at all. You just return a response with an error status code. (2) if you have a big system, you might be storing the "random strings" in different database tables based on some criteria (say, per client application [web vs mobile]). How would you know the table in which to look up the random string? With a JWT token, you can simply add a client_id claim. So, the ability to have information in the token is useful.

Beabeach answered 17/9, 2020 at 0:45 Comment(0)
C
15

You can use express-jwt package

See express-jwt on GitHub or on NPM.

Express-jwt handles revoked tokens as described here: https://github.com/auth0/express-jwt#revoked-tokens

var jwt = require('express-jwt');
var data = require('./data');
var utilities = require('./utilities');

var isRevokedCallback = function(req, payload, done){
  var issuer = payload.iss;
  var tokenId = payload.jti;

  data.getRevokedToken(issuer, tokenId, function(err, token){
    if (err) { return done(err); }
    return done(null, !!token);
  });
};

app.get('/protected',
  jwt({secret: shhhhhhared-secret,
    isRevoked: isRevokedCallback}),
  function(req, res) {
    if (!req.user.admin) return res.send(401);
    res.send(200);
  });

You can also read part 4. How do we avoid adding overhead? from this oauth0 blog post.

Caren answered 8/10, 2015 at 10:12 Comment(0)
E
1

While in most cases JWT tokens should be validated offline, there are cases in which it might make sense to validate online. Your application almost certainly exposes many types of actions, some of which are more risky (and probably less frequent) than others. These risky actions could sometimes benefit from online token validation, since by the time the token is validated, it might not be safe to use it anymore (as it might have been revoked, for instance, which is particularly visible for longer-lived JWTs - which in turn minimize the pressure on refreshes). Another thing is that you might have some sort of a risk engine solution, which can decide whether issuing a token have been a good decision or not.

Ekaterina answered 10/2, 2022 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.