Configure Microsoft Entra ID as the sole SSO provider with allowDangerousEmailAccountLinking to link SSO accounts to existing seeded user records by email match. Add signIn event for automatic org assignment by domain. Guard DEV_BYPASS_AUTH against production use. Add branded pending page for authenticated users without org membership. Remove Google provider for initial rollout simplicity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
69 lines
2 KiB
TypeScript
69 lines
2 KiB
TypeScript
import NextAuth from "next-auth";
|
|
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id";
|
|
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
import { prisma } from "@/lib/prisma";
|
|
import type { Role } from "@/generated/prisma/client";
|
|
|
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
adapter: PrismaAdapter(prisma),
|
|
providers: [
|
|
MicrosoftEntraID({
|
|
clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID,
|
|
clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
|
|
issuer: `https://login.microsoftonline.com/${process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID}/v2.0`,
|
|
// Safe for Entra ID — Microsoft verifies organizational emails.
|
|
// Required to link SSO accounts to pre-seeded User records by email match.
|
|
allowDangerousEmailAccountLinking: true,
|
|
}),
|
|
],
|
|
session: {
|
|
strategy: "database",
|
|
},
|
|
events: {
|
|
async signIn({ user }) {
|
|
if (!user.id || !user.email) return;
|
|
|
|
// Auto-assign organization by email domain match
|
|
const dbUser = await prisma.user.findUnique({
|
|
where: { id: user.id },
|
|
select: { organizationId: true, email: true },
|
|
});
|
|
|
|
if (dbUser?.organizationId || !dbUser?.email) return;
|
|
|
|
const domain = dbUser.email.split("@")[1];
|
|
if (!domain) return;
|
|
|
|
const org = await prisma.organization.findFirst({
|
|
where: { domain },
|
|
});
|
|
|
|
if (org) {
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { organizationId: org.id },
|
|
});
|
|
}
|
|
},
|
|
},
|
|
callbacks: {
|
|
async session({ session, user }) {
|
|
// Fetch user with role and org from database
|
|
const dbUser = await prisma.user.findUnique({
|
|
where: { id: user.id },
|
|
select: { role: true, organizationId: true },
|
|
});
|
|
|
|
if (dbUser) {
|
|
session.user.id = user.id;
|
|
session.user.role = dbUser.role;
|
|
session.user.organizationId = dbUser.organizationId;
|
|
}
|
|
|
|
return session;
|
|
},
|
|
},
|
|
pages: {
|
|
signIn: "/login",
|
|
},
|
|
});
|