Skip to content

Commit ef8a5b1

Browse files
perf: replace jsonwebtoken with jose (#8217)
The jose package has 0 dependencies and is tree shakable ESM. So we get lower bundle size and get rid of 10 dependencies.
1 parent 197e3bc commit ef8a5b1

File tree

8 files changed

+51
-102
lines changed

8 files changed

+51
-102
lines changed

packages/payload/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@
9898
"get-tsconfig": "^4.7.2",
9999
"http-status": "1.6.2",
100100
"image-size": "^1.1.1",
101+
"jose": "5.9.2",
101102
"json-schema-to-typescript": "15.0.1",
102-
"jsonwebtoken": "9.0.2",
103103
"minimist": "1.2.8",
104104
"pino": "9.5.0",
105105
"pino-pretty": "11.3.0",
@@ -114,7 +114,6 @@
114114
"@hyrious/esbuild-plugin-commonjs": "^0.2.4",
115115
"@payloadcms/eslint-config": "workspace:*",
116116
"@types/json-schema": "7.0.15",
117-
"@types/jsonwebtoken": "8.5.9",
118117
"@types/minimist": "1.2.2",
119118
"@types/nodemailer": "6.4.14",
120119
"@types/pluralize": "0.0.33",

packages/payload/src/auth/jwt.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { SignJWT } from 'jose'
2+
3+
export const jwtSign = async ({
4+
fieldsToSign,
5+
secret,
6+
tokenExpiration,
7+
}: {
8+
fieldsToSign: Record<string, unknown>
9+
secret: string
10+
tokenExpiration: number
11+
}) => {
12+
const secretKey = new TextEncoder().encode(secret)
13+
const issuedAt = Math.floor(Date.now() / 1000)
14+
const exp = issuedAt + tokenExpiration
15+
const token = await new SignJWT(fieldsToSign)
16+
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
17+
.setIssuedAt(issuedAt)
18+
.setExpirationTime(exp)
19+
.sign(secretKey)
20+
return { exp, token }
21+
}

packages/payload/src/auth/operations/login.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import jwt from 'jsonwebtoken'
2-
31
import type {
42
AuthOperationsFromCollectionSlug,
53
Collection,
@@ -16,6 +14,7 @@ import { killTransaction } from '../../utilities/killTransaction.js'
1614
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
1715
import { getFieldsToSign } from '../getFieldsToSign.js'
1816
import isLocked from '../isLocked.js'
17+
import { jwtSign } from '../jwt.js'
1918
import { authenticateLocalStrategy } from '../strategies/local/authenticate.js'
2019
import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts.js'
2120
import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts.js'
@@ -234,8 +233,10 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
234233
})) || user
235234
}, Promise.resolve())
236235

237-
const token = jwt.sign(fieldsToSign, secret, {
238-
expiresIn: collectionConfig.auth.tokenExpiration,
236+
const { exp, token } = await jwtSign({
237+
fieldsToSign,
238+
secret,
239+
tokenExpiration: collectionConfig.auth.tokenExpiration,
239240
})
240241

241242
req.user = user
@@ -308,7 +309,7 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
308309
}, Promise.resolve())
309310

310311
let result: { user: DataFromCollectionSlug<TSlug> } & Result = {
311-
exp: (jwt.decode(token) as jwt.JwtPayload).exp,
312+
exp,
312313
token,
313314
user,
314315
}

packages/payload/src/auth/operations/me.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import jwt from 'jsonwebtoken'
1+
import { decodeJwt } from 'jose'
22

33
import type { Collection } from '../../collections/config/types.js'
44
import type { PayloadRequest } from '../../types/index.js'
@@ -70,7 +70,7 @@ export const meOperation = async (args: Arguments): Promise<MeOperationResult> =
7070
result.user = user
7171

7272
if (currentToken) {
73-
const decoded = jwt.decode(currentToken) as jwt.JwtPayload
73+
const decoded = decodeJwt(currentToken)
7474
if (decoded) {
7575
result.exp = decoded.exp
7676
}

packages/payload/src/auth/operations/refresh.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import jwt from 'jsonwebtoken'
21
import url from 'url'
32

43
import type { BeforeOperationHook, Collection } from '../../collections/config/types.js'
@@ -10,6 +9,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
109
import { initTransaction } from '../../utilities/initTransaction.js'
1110
import { killTransaction } from '../../utilities/killTransaction.js'
1211
import { getFieldsToSign } from '../getFieldsToSign.js'
12+
import { jwtSign } from '../jwt.js'
1313

1414
export type Result = {
1515
exp: number
@@ -102,12 +102,12 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
102102
user: args?.req?.user,
103103
})
104104

105-
const refreshedToken = jwt.sign(fieldsToSign, secret, {
106-
expiresIn: collectionConfig.auth.tokenExpiration,
105+
const { exp, token: refreshedToken } = await jwtSign({
106+
fieldsToSign,
107+
secret,
108+
tokenExpiration: collectionConfig.auth.tokenExpiration,
107109
})
108110

109-
const exp = (jwt.decode(refreshedToken) as Record<string, unknown>).exp as number
110-
111111
result = {
112112
exp,
113113
refreshedToken,

packages/payload/src/auth/operations/resetPassword.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import httpStatus from 'http-status'
2-
import jwt from 'jsonwebtoken'
32

43
import type { Collection } from '../../collections/config/types.js'
54
import type { PayloadRequest } from '../../types/index.js'
@@ -9,6 +8,7 @@ import { commitTransaction } from '../../utilities/commitTransaction.js'
98
import { initTransaction } from '../../utilities/initTransaction.js'
109
import { killTransaction } from '../../utilities/killTransaction.js'
1110
import { getFieldsToSign } from '../getFieldsToSign.js'
11+
import { jwtSign } from '../jwt.js'
1212
import { authenticateLocalStrategy } from '../strategies/local/authenticate.js'
1313
import { generatePasswordSaltHash } from '../strategies/local/generatePasswordSaltHash.js'
1414

@@ -118,8 +118,10 @@ export const resetPasswordOperation = async (args: Arguments): Promise<Result> =
118118
user,
119119
})
120120

121-
const token = jwt.sign(fieldsToSign, secret, {
122-
expiresIn: collectionConfig.auth.tokenExpiration,
121+
const { token } = await jwtSign({
122+
fieldsToSign,
123+
secret,
124+
tokenExpiration: collectionConfig.auth.tokenExpiration,
123125
})
124126

125127
const fullUser = await payload.findByID({

packages/payload/src/auth/strategies/jwt.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import jwt from 'jsonwebtoken'
1+
import { jwtVerify } from 'jose'
22

33
import type { Payload, Where } from '../../types/index.js'
44
import type { AuthStrategyFunction, User } from '../index.js'
@@ -81,8 +81,8 @@ export const JWTAuthentication: AuthStrategyFunction = async ({
8181
return { user: null }
8282
}
8383

84-
const decodedPayload = jwt.verify(token, payload.secret) as jwt.JwtPayload & JWTToken
85-
84+
const secretKey = new TextEncoder().encode(payload.secret)
85+
const { payload: decodedPayload } = await jwtVerify<JWTToken>(token, secretKey)
8686
const collection = payload.collections[decodedPayload.collection]
8787

8888
const user = await payload.findByID({

pnpm-lock.yaml

Lines changed: 8 additions & 82 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)