diff --git a/docs/authentication/overview.mdx b/docs/authentication/overview.mdx index 0bf7b6a92f..32bedf00ce 100644 --- a/docs/authentication/overview.mdx +++ b/docs/authentication/overview.mdx @@ -83,9 +83,11 @@ Once enabled, each document that is created within the Collection can be thought Successfully logging in returns a `JWT` (JSON web token) which is how a user will identify themselves to Payload. By providing this JWT via either an HTTP-only cookie or an `Authorization` header, Payload will automatically identify the user and add its user JWT data to the Express `req`, which is available throughout Payload including within access control, hooks, and more. +You can specify what data gets encoded to the JWT token by setting `saveToJWT` to true in your auth collection fields. If you wish to use a different key other than the field `name`, you can provide it to `saveToJWT` as a string. + Tip:
- You can access the logged in user from access control functions and hooks via the Express req. The logged in user is automatically added as the user property. + You can access the logged-in user from access control functions and hooks via the Express req. The logged-in user is automatically added as the user property.
### HTTP-only cookies diff --git a/src/auth/operations/getFieldsToSign.ts b/src/auth/operations/getFieldsToSign.ts index e5a86cf9e1..83ad020bd0 100644 --- a/src/auth/operations/getFieldsToSign.ts +++ b/src/auth/operations/getFieldsToSign.ts @@ -18,16 +18,17 @@ export const getFieldsToSign = (args: { ...signedFields, }; + // get subfields from non-named fields like rows if (!fieldAffectsData(field) && fieldHasSubFields(field)) { field.fields.forEach((subField) => { if (fieldAffectsData(subField) && subField.saveToJWT) { - result[subField.name] = user[subField.name]; + result[typeof subField.saveToJWT === 'string' ? subField.saveToJWT : subField.name] = user[subField.name]; } }); } if (fieldAffectsData(field) && field.saveToJWT) { - result[field.name] = user[field.name]; + result[typeof field.saveToJWT === 'string' ? field.saveToJWT : field.name] = user[field.name]; } return result; diff --git a/src/auth/operations/resetPassword.ts b/src/auth/operations/resetPassword.ts index c4e04830f8..20635c2943 100644 --- a/src/auth/operations/resetPassword.ts +++ b/src/auth/operations/resetPassword.ts @@ -3,7 +3,7 @@ import { Response } from 'express'; import { Collection } from '../../collections/config/types'; import { APIError } from '../../errors'; import getCookieExpiration from '../../utilities/getCookieExpiration'; -import { fieldAffectsData } from '../../fields/config/types'; +import { getFieldsToSign } from './getFieldsToSign'; import { PayloadRequest } from '../../express/types'; import { authenticateLocalStrategy } from '../strategies/local/authenticate'; import { generatePasswordSaltHash } from '../strategies/local/generatePasswordSaltHash'; @@ -83,18 +83,10 @@ async function resetPassword(args: Arguments): Promise { await authenticateLocalStrategy({ password: data.password, doc }); - const fieldsToSign = collectionConfig.fields.reduce((signedFields, field) => { - if (fieldAffectsData(field) && field.saveToJWT) { - return { - ...signedFields, - [field.name]: user[field.name], - }; - } - return signedFields; - }, { + const fieldsToSign = getFieldsToSign({ + collectionConfig, + user, email: user.email, - id: user.id, - collection: collectionConfig.slug, }); const token = jwt.sign( diff --git a/src/fields/config/schema.ts b/src/fields/config/schema.ts index 2d1f2dd732..c1125aa6f9 100644 --- a/src/fields/config/schema.ts +++ b/src/fields/config/schema.ts @@ -33,7 +33,10 @@ export const baseField = joi.object().keys({ joi.valid(false), ), required: joi.boolean().default(false), - saveToJWT: joi.boolean().default(false), + saveToJWT: joi.alternatives().try( + joi.boolean(), + joi.string(), + ).default(false), unique: joi.boolean().default(false), localized: joi.boolean().default(false), index: joi.boolean().default(false), diff --git a/src/fields/config/types.ts b/src/fields/config/types.ts index 6b0bdc234c..edbc0b6e8b 100644 --- a/src/fields/config/types.ts +++ b/src/fields/config/types.ts @@ -1,7 +1,7 @@ /* eslint-disable no-use-before-define */ import { CSSProperties } from 'react'; import { Editor } from 'slate'; -import type { TFunction, i18n as Ii18n } from 'i18next'; +import type { i18n as Ii18n, TFunction } from 'i18next'; import type { EditorProps } from '@monaco-editor/react'; import { Operation, Where } from '../../types'; import { SanitizedConfig } from '../../config/types'; @@ -108,7 +108,7 @@ export interface FieldBase { index?: boolean; defaultValue?: any; hidden?: boolean; - saveToJWT?: boolean + saveToJWT?: string | boolean; localized?: boolean; validate?: Validate; hooks?: { diff --git a/test/auth/config.ts b/test/auth/config.ts index 0e396976db..d406618392 100644 --- a/test/auth/config.ts +++ b/test/auth/config.ts @@ -6,6 +6,10 @@ import { AuthDebug } from './AuthDebug'; export const slug = 'users'; +export const namedSaveToJWTValue = 'namedSaveToJWT value'; + +export const saveToJWTKey = 'x-custom-jwt-property-name'; + export default buildConfigWithDefaults({ admin: { user: 'users', @@ -38,6 +42,12 @@ export default buildConfigWithDefaults({ saveToJWT: true, hasMany: true, }, + { + name: 'namedSaveToJWT', + type: 'text', + defaultValue: namedSaveToJWTValue, + saveToJWT: saveToJWTKey, + }, { name: 'custom', label: 'Custom', diff --git a/test/auth/int.spec.ts b/test/auth/int.spec.ts index 6d5cd1547f..af866e4d61 100644 --- a/test/auth/int.spec.ts +++ b/test/auth/int.spec.ts @@ -1,7 +1,8 @@ import mongoose from 'mongoose'; +import jwtDecode from 'jwt-decode'; import payload from '../../src'; import { initPayloadTest } from '../helpers/configHelpers'; -import { slug } from './config'; +import { namedSaveToJWTValue, saveToJWTKey, slug } from './config'; import { devUser } from '../credentials'; import type { User } from '../../src/auth'; @@ -101,6 +102,24 @@ describe('Auth', () => { expect(data.user.email).toBeDefined(); }); + it('should have fields saved to JWT', async () => { + const { + email: jwtEmail, + collection, + roles, + [saveToJWTKey]: customJWTPropertyKey, + iat, + exp, + } = jwtDecode(token); + + expect(jwtEmail).toBeDefined(); + expect(collection).toEqual('users'); + expect(Array.isArray(roles)).toBeTruthy(); + // 'x-custom-jwt-property-name': 'namedSaveToJWT value' + expect(customJWTPropertyKey).toEqual(namedSaveToJWTValue); + expect(iat).toBeDefined(); + expect(exp).toBeDefined(); + }); it('should allow authentication with an API key with useAPIKey', async () => { const apiKey = '0123456789ABCDEFGH';