feat: allow specification of which JWT extraction methods are supported, and in which order (#10794)

This PR adds a top-level `auth` property to the Payload config, where
you can specify a new `jwtOrder` property to dictate, in Payload's local
auth strategy, which JWT extraction methods should be leveraged, and in
which order.

For example, we currently use incoming request headers to retrieve a JWT
in the following order:

1. If there is an `Authorization: JWT ${token}` header
2. If there is an `Authorization: Bearer ${token}` header
3. If there is an HTTP-only cookie with a token present

Now you can define which of these strategies you'd like to support, and
in which order.

Todo: 
- [ ] Docs
- [ ] Tests
This commit is contained in:
James Mikrut
2025-03-05 15:56:40 -06:00
committed by GitHub
parent 54acdad190
commit 8f6d2e79a1
3 changed files with 61 additions and 20 deletions

View File

@@ -1,32 +1,60 @@
import type { BasePayload } from '../index.js'
import type { AuthStrategyFunctionArgs } from './index.js'
import { parseCookies } from '../utilities/parseCookies.js'
type ExtractionMethod = (args: { headers: Headers; payload: BasePayload }) => null | string
const extractionMethods: Record<string, ExtractionMethod> = {
Bearer: ({ headers }) => {
const jwtFromHeader = headers.get('Authorization')
// allow RFC6750 OAuth 2.0 compliant Bearer tokens
// in addition to the payload default JWT format
if (jwtFromHeader?.startsWith('Bearer ')) {
return jwtFromHeader.replace('Bearer ', '')
}
return null
},
cookie: ({ headers, payload }) => {
const origin = headers.get('Origin')
const cookies = parseCookies(headers)
const tokenCookieName = `${payload.config.cookiePrefix}-token`
const cookieToken = cookies.get(tokenCookieName)
if (!cookieToken) {
return null
}
if (!origin || payload.config.csrf.length === 0 || payload.config.csrf.indexOf(origin) > -1) {
return cookieToken
}
return null
},
JWT: ({ headers }) => {
const jwtFromHeader = headers.get('Authorization')
if (jwtFromHeader?.startsWith('JWT ')) {
return jwtFromHeader.replace('JWT ', '')
}
return null
},
}
export const extractJWT = (args: Omit<AuthStrategyFunctionArgs, 'strategyName'>): null | string => {
const { headers, payload } = args
const jwtFromHeader = headers.get('Authorization')
const origin = headers.get('Origin')
const extractionOrder = payload.config.auth.jwtOrder
if (jwtFromHeader?.startsWith('JWT ')) {
return jwtFromHeader.replace('JWT ', '')
}
// allow RFC6750 OAuth 2.0 compliant Bearer tokens
// in addition to the payload default JWT format
if (jwtFromHeader?.startsWith('Bearer ')) {
return jwtFromHeader.replace('Bearer ', '')
}
for (const extractionStrategy of extractionOrder) {
const result = extractionMethods[extractionStrategy]({ headers, payload })
const cookies = parseCookies(headers)
const tokenCookieName = `${payload.config.cookiePrefix}-token`
const cookieToken = cookies.get(tokenCookieName)
if (!cookieToken) {
return null
}
if (!origin || payload.config.csrf.length === 0 || payload.config.csrf.indexOf(origin) > -1) {
return cookieToken
if (result) {
return result
}
}
return null

View File

@@ -32,6 +32,9 @@ export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
},
theme: 'all',
},
auth: {
jwtOrder: ['JWT', 'Bearer', 'cookie'],
},
bin: [],
collections: [],
cookiePrefix: 'payload',

View File

@@ -946,6 +946,16 @@ export type Config = {
/** The slug of a Collection that you want to be used to log in to the Admin dashboard. */
user?: string
}
/**
* Configure authentication-related Payload-wide settings.
*/
auth?: {
/**
* Define which JWT identification methods you'd like to support for Payload's local auth strategy, as well as the order that they're retrieved in.
* Defaults to ['JWT', 'Bearer', 'cookie]
*/
jwtOrder: ('Bearer' | 'cookie' | 'JWT')[]
}
/** Custom Payload bin scripts can be injected via the config. */
bin?: BinScriptConfig[]
blocks?: Block[]