A Payload CMS 3 plugin for integrating Auth.js 5 (beta).
⚠ This plugin and Auth.js is in beta and may have some bugs. Please report any issues you find.
Install the plugin using any JavaScript package manager like PNPM, NPM, or Yarn:
pnpm i payload-authjs
Fist of all, setup Auth.js like you would do in a Next.js application. You can follow the Auth.js guide.
⚠ Make sure you define your config in a separate file (e.g.
auth.config.ts
) than where you create the NextAuth instance (e.g.auth.ts
) to avoid circular dependencies. ⚠
// auth.config.ts
import github from "next-auth/providers/github";
export const authConfig: NextAuthConfig = {
providers: [
github, // <-- Add your provider here
],
};
Wrap your Auth.js configuration with the withPayload
function before creating the NextAuth instance:
// auth.ts
import payloadConfig from "@payload-config";
import NextAuth from "next-auth";
import { withPayload } from "payload-authjs";
import { authConfig } from "./auth.config"; // ⚠ Import the config from a separate file
export const { handlers, signIn, signOut, auth } = NextAuth(
withPayload(authConfig, {
payloadConfig,
}),
);
Add the authjsPlugin
in your Payload configuration file:
// payload.config.ts
import { authjsPlugin } from "payload-authjs";
import { authConfig } from "./auth.config";
export const config = buildConfig({
plugins: [
authjsPlugin({
authjsConfig: authConfig,
}),
],
});
And that's it! Now you can sign-in via Auth.js and you are automatically authenticated in Payload CMS. Nice 🎉
You don't need to create a collection for users. This plugin automatically creates a collection with the slug
users
.
If you want to customize the users collection, you can create a collection with the slug users
and add your customizations there.
// users.ts
const Users: CollectionConfig = {
slug: "users",
fields: [],
};
Click to expand
You can customize the existing fields in the users collection by adding the field to the collection and modifying the field. The fields will be merged together.
// users.ts
const Users: CollectionConfig = {
slug: "users",
fields: [
{
name: "id",
type: "text",
label: "Identifier", // <-- Add a label to the id field
admin: {
hidden: true, // <-- Hide id field in admin panel
},
},
{
name: "accounts",
type: "array",
fields: [
{
name: "provider",
type: "text",
label: "Account Provider", // <-- Add label to provider field
},
],
},
],
};
You can also add additional fields to the users collection.
Click to expand
There are 2 ways to add additional fields. It depends on the data you want to store and your Auth.js session strategy (session.strategy
).
If you want to store additional data in the database, you can add a new field to the users collection and extend your Auth.js provider to include the new field in the user.
For example, you could add a locale
field to the users collection:
// users.ts
const Users: CollectionConfig = {
slug: "users",
fields: [
// Add custom field for 'locale'
{
name: "locale",
type: "text",
},
],
};
Next, you need to extend the user object returned by your Auth.js provider. You can do this as shown in this example:
// auth.config.ts
const authConfig: NextAuthConfig = {
providers: [
keycloak({
/**
* Add additional fields to the user on first sign in
*/
profile(profile) {
return {
// Default fields from keycloak provider
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
// Custom fields
locale: profile.locale, // <-- Add your custom field (e.g. get locale from the profile)
};
},
}),
],
...
};
⚠ Keep in mind that Auth.js doesn't update the user after the first sign-in. If you want to update the user on every sign-in, you can use the
signIn
event. (See Events)
If you are using the Auth.js jwt
session strategy (it's the default), you can use a virtual field to add additional data that should not be stored in the database.
This plugin extract the virtual fields from your Auth.js jwt session and adds them to the user object.
For example, you could add a roles
field to the users collection:
// users.ts
const Users: CollectionConfig = {
slug: "users",
fields: [
// Add custom field for 'roles'
{
name: "roles",
type: "json",
virtual: true, // <-- Make the field virtual
admin: {
hidden: true,
},
},
],
};
Next, you need to extend your Auth.js session with your field. You can do this as shown in this example:
// auth.config.ts
const authConfig: NextAuthConfig = {
callbacks: {
jwt: ({ token, trigger }) => {
if (trigger === "signIn" || trigger === "signUp") {
token.roles = ["example-role"]; // <-- Add your virtual field to the token
}
return token;
},
session: ({ session, token }) => {
if (token) {
session.user.roles = token.roles; // <-- Forward the virtual field from the token to the session
}
return session;
},
},
...
};
At this point, you can implement your own logic to extend the session. E.g. extract from profile, fetch from a server, or something else.
More information about extending your session can be found in the Auth.js documentation.
Now you could access your custom field, e.g. in the access control operations or somewhere else:
const Examples: CollectionConfig = {
slug: "examples",
access: {
read: ({ req: { user } }) => {
return user?.roles?.includes("user") ?? false; // <-- Check if the user has the role "user"
},
},
fields: [
...
],
};
If you are using typescript you can declare your Auth.js user type as shown in the following example:
// auth.config.ts
import type { PayloadAuthjsUser } from "payload-authjs";
import type { User as PayloadUser } from "payload/generated-types";
declare module "next-auth" {
interface User extends PayloadAuthjsUser<PayloadUser> {}
}
More information about typescript can be found in the Auth.js documentation.
Auth.js emits some events that you can listen to. This plugin extends the events with additional parameters like the adapter
and payload
instance.
More information about the events can be found in the Auth.js documentation.
The following events are available:
- signIn
- signOut
- createUser
- updateUser
- linkAccount
- session
The signIn
event is emitted when a user successfully signs in. For example, you could use this event to update the user's name on every sign-in:
// auth.ts
export const { handlers, signIn, signOut, auth } = NextAuth(
withPayload(authConfig, {
payloadConfig,
events: {
/**
* Update user 'name' on every sign in
*/
signIn: async ({ adapter, user, profile }) => {
if (!user.id || !profile) {
return;
}
await adapter.updateUser!({
id: user.id,
name: profile.name ?? (profile.login as string | undefined),
});
},
},
}),
);
This plugin also export a utility function to get the current payload user in the server-side:
// ServerComponentExample.tsx
const ServerComponentExample = async () => {
const payloadUser = await getPayloadUser();
return (
<div>
<h3>Payload CMS User</h3>
<div>{JSON.stringify(payloadUser)}</div>
</div>
);
};