Skip to content

Instantly share code, notes, and snippets.

@mariovalney
Last active February 10, 2025 22:33
Show Gist options
  • Save mariovalney/61a16ebd43dc800d97ce1997b2eb3d84 to your computer and use it in GitHub Desktop.
Save mariovalney/61a16ebd43dc800d97ce1997b2eb3d84 to your computer and use it in GitHub Desktop.
NextAuth + PayloadCMS (Azure AD)
import NextAuth from 'next-auth';
import AzureAD from 'next-auth/providers/azure-ad';
import { decodeJwt } from 'jose';
import { createOrUpdate } from '@utils/database';
import { Users, UsersSlug } from '@collections/Users';
/**
* Sync data from token (Azure User) to database
*
* @param {Object} data
* @returns {Promise<import('payload').User>}
*/
const syncDatabaseUser = async (data) => {
const user = await createOrUpdate(UsersSlug, { email: { equals: data.email } }, data);
// Add more data if necessary
return user;
}
/**
* Add user data from token to current session
*
* @param {Object} session
* @param {Object} token
* @returns {Object}
*/
const prepareSessionFromToken = (session, token) => {
session.user.id = token.id;
for (const field of Users.fields) {
if (!('name' in field) || !(field.name in token)) {
continue;
}
session.user[field.name] = token[field.name];
}
return session;
}
const AzureADProvider = (typeof AzureAD === 'function') ? AzureAD : (a) => a;
const options = {
debug: (process.env?.NEXT_AUTH_DEBUG || '0') === '1',
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID || '',
clientSecret: process.env.AZURE_AD_CLIENT_SECRET || '',
tenantId: process.env.AZURE_AD_TENANT_ID,
authorization: {
params: {
scope: `openid profile email user.read`,
prompt: 'select_account'
}
}
})
],
session: {
maxAge: 7 * 24 * 60 * 60, // 7 days @see collections\Users.ts,
},
callbacks: {
async jwt({ token, account }) {
if (! token || ! token?.email || ! account?.id_token) {
return token;
}
token.image = token.picture;
// Add data to token from idToken
// const idToken = decodeJwt(account.id_token);
// token.example = idToken.example;
return await syncDatabaseUser(token);
},
async session({ session, token }) {
return prepareSessionFromToken(session, token);
}
}
};
export const authOptions = options;
export default async function auth(req, res) {
return await NextAuth(req, res, options);
}
import { getServerSession } from 'next-auth/next';
import { authOptions } from '@pages/api/auth/[...nextauth]';
import type { CollectionSlug, CollectionConfig } from 'payload'
import { User } from '../payload-types';
export const Users: CollectionConfig = {
slug: 'users',
admin: {
hidden: true,
useAsTitle: 'name',
},
auth: {
verify: false,
disableLocalStrategy: true,
removeTokenFromResponses: true,
tokenExpiration: 7 * 24 * 60 * 60, // 7 days @see pages\api\auth\[...nextauth],
strategies: [
{
name: 'next-auth-AzureADProvider',
authenticate: async ({ payload }) => {
const session = await getServerSession(authOptions);
if (! session || ! session?.user?.email) {
return { user: null };
}
const found = await payload.find({ collection: UsersSlug, where: { email: { equals: session.user.email } } });
const user = (found.docs[0] || null) as User;
if (! user) {
return { user: null };
}
return { user: { collection: UsersSlug, ...user } };
}
}
]
},
fields: [
// Your fields
]
}
@mariovalney
Copy link
Author

Note: "createOrUpdate" is a simple "try to find" them "update" or "create".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment