dow-prod-tracker/src/lib/auth.ts
Vadym Samoilenko 6fd240860c Fix SSO redirect URI by setting authorization.params explicitly
next-auth v5 beta ignores redirectProxyUrl when constructing the
redirect_uri sent to Microsoft — it strips the pathname from AUTH_URL
and uses only the origin. Passing redirect_uri directly in
authorization.params guarantees the /hp-prod-tracker basePath is
included in the callback URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:04:42 +01:00

86 lines
2.9 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";
// next-auth v5 beta ignores redirectProxyUrl when building the redirect_uri
// sent to the OAuth provider — it strips the pathname from AUTH_URL and appends
// basePath directly to the origin. We must pass redirect_uri explicitly so the
// /hp-prod-tracker basePath is included in the Microsoft callback URL.
const explicitRedirectUri = process.env.AUTH_URL
? `${process.env.AUTH_URL}/api/auth/callback/microsoft-entra-id`
: undefined;
export const { handlers, auth, signIn, signOut } = NextAuth({
// Explicit basePath prevents AUTH_URL's pathname from overriding it.
// Next.js strips the /hp-prod-tracker prefix before Auth.js sees the request,
// so internally routes must match /api/auth/*.
basePath: "/api/auth",
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,
// Explicitly set redirect_uri so /hp-prod-tracker basePath is included.
// next-auth v5 beta strips the pathname from AUTH_URL otherwise.
...(explicitRedirectUri && {
authorization: { params: { redirect_uri: explicitRedirectUri } },
}),
}),
],
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",
},
});