Skip to content

Commit

Permalink
add an optional handler for expired tokens. closes #6048
Browse files Browse the repository at this point in the history
  • Loading branch information
jfromaniello committed Dec 22, 2022
1 parent 4b8f1e6 commit ca6c90c
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 2 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Options has the following parameters:
- `secret: jwt.Secret | GetVerificationKey` (required): The secret as a string or a function to retrieve the secret.
- `getToken?: TokenGetter` (optional): A function that receives the express `Request` and returns the token, by default it looks in the `Authorization` header.
- `isRevoked?: IsRevoked` (optional): A function to verify if a token is revoked.
- `onExpired?: ExpirationHandler` (optional): A function to handle expired tokens.
- `credentialsRequired?: boolean` (optional): If its false, continue to the next middleware if the request does not contain a token instead of failing, defaults to true.
- `requestProperty?: string` (optional): Name of the property in the request object where the payload is set. Default to `req.auth`.
- Plus... all the options available in the [jsonwebtoken verify function](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback).
Expand Down Expand Up @@ -211,6 +212,21 @@ app.get(
);
```

### Handling expired tokens

You can handle expired tokens as follows:

```javascript
jwt({
secret: "shhhhhhared-secret",
algorithms: ["HS256"],
onExpired: async (req, err) => {
if (new Date() - err.inner.expiredAt < 5000) { return;}
throw err;
},,
})
```

### Error handling

The default behavior is to throw an error when the token is invalid, so you can add your custom logic to manage unauthorized access as follows:
Expand Down
19 changes: 17 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export type SecretCallbackLong = GetVerificationKey;
*/
export type IsRevoked = (req: express.Request, token: jwt.Jwt | undefined) => boolean | Promise<boolean>;

/**
* A function to check if a token is revoked
*/
export type ExpirationHandler = (req: express.Request, err: UnauthorizedError) => void | Promise<void>;

/**
* A function to customize how a token is retrieved from the express request.
*/
Expand Down Expand Up @@ -62,7 +67,12 @@ export type Params = {
/**
* List of JWT algorithms allowed.
*/
algorithms: jwt.Algorithm[];
algorithms: jwt.Algorithm[],

/**
* Handle expired tokens.
*/
onExpired?: ExpirationHandler,
} & jwt.VerifyOptions;

export { UnauthorizedError } from './errors/UnauthorizedError';
Expand Down Expand Up @@ -161,7 +171,12 @@ export const expressjwt = (options: Params) => {
try {
jwt.verify(token, key, options);
} catch (err) {
throw new UnauthorizedError('invalid_token', err);
const wrappedErr = new UnauthorizedError('invalid_token', err);
if (err instanceof jwt.TokenExpiredError && typeof options.onExpired === 'function') {
await options.onExpired(req, wrappedErr);
} else {
throw wrappedErr;
}
}

const isRevoked = options.isRevoked && await options.isRevoked(req, decodedToken) || false;
Expand Down
38 changes: 38 additions & 0 deletions test/jwt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,44 @@ describe('failure tests', function () {
});
});

it('should not throw if token is expired but the expired handler let it thru', function (done) {
const secret = 'shhhhhh';
const token = jwt.sign({ foo: 'bar', exp: 1382412921 }, secret);

req.headers = {};
req.headers.authorization = 'Bearer ' + token;
expressjwt({
secret: 'shhhhhh',
algorithms: ['HS256'],
// eslint-disable-next-line @typescript-eslint/no-empty-function
onExpired: () => { },
})(req, res, function (err) {
assert.ok(!err);
assert.equal(req.auth.foo, 'bar');
done();
});
});

it('should throw if token is expired and the expired handler rethrows it', function (done) {
const secret = 'shhhhhh';
const token = jwt.sign({ foo: 'bar', exp: 1382412921 }, secret);

req.headers = {};
req.headers.authorization = 'Bearer ' + token;
expressjwt({
secret: 'shhhhhh',
algorithms: ['HS256'],
// eslint-disable-next-line @typescript-eslint/no-empty-function
onExpired: (req, err) => { throw err; },
})(req, res, function (err) {
assert.ok(err);
assert.equal(err.code, 'invalid_token');
assert.equal(err.inner.name, 'TokenExpiredError');
assert.equal(err.message, 'jwt expired');
done();
});
});

it('should throw if token issuer is wrong', function (done) {
const secret = 'shhhhhh';
const token = jwt.sign({ foo: 'bar', iss: 'http://foo' }, secret);
Expand Down

0 comments on commit ca6c90c

Please sign in to comment.