feat: set JWT token field name with saveToJWT (#3126)
This commit is contained in:
@@ -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.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
You can access the logged in user from access control functions and hooks via the Express <strong>req</strong>. The logged in user is automatically added as the <strong>user</strong> property.
|
||||
You can access the logged-in user from access control functions and hooks via the Express <strong>req</strong>. The logged-in user is automatically added as the <strong>user</strong> property.
|
||||
</Banner>
|
||||
|
||||
### HTTP-only cookies
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Result> {
|
||||
|
||||
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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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?: {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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<User>(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';
|
||||
|
||||
Reference in New Issue
Block a user