obsidian/wiki/payloadcms/authentication-jwt.md
2026-05-15 15:13:56 +01:00

2.9 KiB

title aliases tags sources created updated
Authentication — JWT Strategy
payload-jwt
jwt-auth-payload
payloadcms
authentication
jwt
security
raw/authentication__jwt.md
2026-05-15 2026-05-15

Overview

Payload supports JWT-based authentication alongside wiki/payloadcms/authentication-cookies. Tokens are returned from login, logout, refresh, and me operations.

Using the Authorization Header

Pass the JWT via Authorization header for stateless/API clients:

// 1. Login to get token
const { token } = await fetch('http://localhost:3000/api/users/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'dev@payloadcms.com', password: 'password' }),
}).then(r => r.json())

// 2. Authenticate subsequent requests
const response = await fetch('http://localhost:3000/api/posts', {
  headers: { Authorization: `JWT ${token}` },
})
  • Format: Authorization: JWT <token> (not Bearer)
  • Alternative to cookie — useful for mobile apps, external services, scripts

Accessing the Logged-In User

Within wiki/payloadcms/local-api and hooks, use req.user:

access: {
  read: ({ req }) => req.user?.role === 'admin',
}

Removing Token from Responses

If you don't want the JWT returned in API responses (e.g., cookie-only flows):

export const Users: CollectionConfig = {
  slug: 'users',
  auth: {
    removeTokenFromResponses: true,
  },
}

External JWT Validation

When validating Payload-issued JWTs in external services, you must replicate Payload's secret processing — it doesn't use your raw PAYLOAD_SECRET:

import crypto from 'node:crypto'

// Payload's actual signing secret:
const secret = crypto
  .createHash('sha256')
  .update(process.env.PAYLOAD_SECRET)
  .digest('hex')
  .slice(0, 32)
  • Payload SHA-256 hashes the env secret, then takes the first 32 hex characters
  • Use this derived value in your external verifier (e.g., jsonwebtoken.verify(token, secret))
  • Gotcha: using the raw PAYLOAD_SECRET in an external verifier will fail silently

Key Takeaways

  • JWT header format is Authorization: JWT <token>not Bearer
  • req.user is available in all access control functions and hooks
  • removeTokenFromResponses: true hides the token from API responses without disabling JWT auth
  • External validation requires deriving the signing secret via SHA-256 hash + first 32 chars
  • Cookie and JWT auth can coexist — each request uses whichever is present