UNPKG

3.87 kBPlain TextView Raw
1import * as jwt from 'jsonwebtoken';
2import * as express from 'express';
3import expressUnless from 'express-unless';
4import { UnauthorizedError } from './errors/UnauthorizedError';
5
6export type GetVerificationKey = (req: express.Request, token: jwt.Jwt | undefined) => Promise<jwt.Secret>;
7export type IsRevoked = (req: express.Request, token: jwt.Jwt | undefined) => Promise<boolean>;
8
9type TokenGetter = (req: express.Request) => string | Promise<string> | undefined;
10
11type Params = {
12 secret: jwt.Secret | GetVerificationKey,
13 getToken?: TokenGetter,
14 isRevoked?: IsRevoked,
15 credentialsRequired?: boolean,
16} & jwt.VerifyOptions;
17
18export { UnauthorizedError } from './errors/UnauthorizedError';
19
20export type ExpressJwtRequest<T = jwt.JwtPayload> =
21 express.Request & { auth: T }
22
23
24export const expressjwt = (options: Params) => {
25 if (!options?.secret) throw new RangeError('express-jwt: `secret` is a required option');
26 if (!options.algorithms) throw new RangeError('express-jwt: `algorithms` is a required option');
27 if (!Array.isArray(options.algorithms)) throw new RangeError('express-jwt: `algorithms` must be an array');
28
29 const getVerificationKey: GetVerificationKey =
30 typeof options.secret === 'function' ?
31 options.secret :
32 async () => options.secret as jwt.Secret;
33
34 const credentialsRequired = typeof options.credentialsRequired === 'undefined' ? true : options.credentialsRequired;
35
36
37 const middleware = async function (req: express.Request, res: express.Response, next: express.NextFunction) {
38 let token: string;
39 try {
40 if (req.method === 'OPTIONS' && 'access-control-request-headers' in req.headers) {
41 const hasAuthInAccessControl = req.headers['access-control-request-headers']
42 .split(',')
43 .map(header => header.trim())
44 .includes('authorization');
45 if (hasAuthInAccessControl) {
46 return next();
47 }
48 }
49
50 if (options.getToken && typeof options.getToken === 'function') {
51 token = await options.getToken(req);
52 } else if (req.headers && req.headers.authorization) {
53 const parts = req.headers.authorization.split(' ');
54 if (parts.length == 2) {
55 const scheme = parts[0];
56 const credentials = parts[1];
57
58 if (/^Bearer$/i.test(scheme)) {
59 token = credentials;
60 } else {
61 if (credentialsRequired) {
62 throw new UnauthorizedError('credentials_bad_scheme', { message: 'Format is Authorization: Bearer [token]' });
63 } else {
64 return next();
65 }
66 }
67 } else {
68 throw new UnauthorizedError('credentials_bad_format', { message: 'Format is Authorization: Bearer [token]' });
69 }
70 }
71
72 if (!token) {
73 if (credentialsRequired) {
74 throw new UnauthorizedError('credentials_required', { message: 'No authorization token was found' });
75 } else {
76 return next();
77 }
78 }
79
80 let decodedToken: jwt.Jwt;
81
82 try {
83 decodedToken = jwt.decode(token, { complete: true });
84 } catch (err) {
85 throw new UnauthorizedError('invalid_token', err);
86 }
87
88 const key = await getVerificationKey(req, decodedToken);
89
90 try {
91 jwt.verify(token, key, options);
92 } catch (err) {
93 throw new UnauthorizedError('invalid_token', err);
94 }
95
96 const isRevoked = options.isRevoked && await options.isRevoked(req, decodedToken) || false;
97 if (isRevoked) {
98 throw new UnauthorizedError('revoked_token', { message: 'The token has been revoked.' });
99 }
100
101 const request = req as ExpressJwtRequest<jwt.JwtPayload | string>;
102 request.auth = decodedToken.payload;
103 next();
104 } catch (err) {
105 return next(err);
106 }
107 };
108
109 middleware.unless = expressUnless;
110
111 return middleware;
112}
113
114