diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
new file mode 100644
index 0000000..9e74013
--- /dev/null
+++ b/src/app/(auth)/login/page.tsx
@@ -0,0 +1,89 @@
+import { auth, signIn } from "@/lib/auth";
+import { redirect } from "next/navigation";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+
+export default async function LoginPage() {
+ const session = await auth();
+
+ if (session) {
+ redirect("/dashboard");
+ }
+
+ return (
+
+
+
+
+ HP CG Production Tracker
+
+ Sign in to manage your production pipeline
+
+
+
+
+
+
+
+ );
+}
+
+function GoogleIcon() {
+ return (
+
+ );
+}
+
+function MicrosoftIcon() {
+ return (
+
+ );
+}
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 0000000..c55a45e
--- /dev/null
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,3 @@
+import { handlers } from "@/lib/auth";
+
+export const { GET, POST } = handlers;
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
new file mode 100644
index 0000000..aeef523
--- /dev/null
+++ b/src/lib/auth.ts
@@ -0,0 +1,44 @@
+import NextAuth from "next-auth";
+import Google from "next-auth/providers/google";
+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: [
+ Google({
+ clientId: process.env.AUTH_GOOGLE_ID,
+ clientSecret: process.env.AUTH_GOOGLE_SECRET,
+ }),
+ 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`,
+ }),
+ ],
+ session: {
+ strategy: "database",
+ },
+ 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",
+ },
+});
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..e4dd454
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,36 @@
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+
+export function middleware(request: NextRequest) {
+ const { pathname } = request.nextUrl;
+ const isAuthPage = pathname.startsWith("/login");
+ const isApiAuth = pathname.startsWith("/api/auth");
+
+ // Always allow auth API routes
+ if (isApiAuth) {
+ return NextResponse.next();
+ }
+
+ // Check for session cookie (Auth.js database sessions)
+ const sessionToken =
+ request.cookies.get("authjs.session-token")?.value ||
+ request.cookies.get("__Secure-authjs.session-token")?.value;
+
+ const isLoggedIn = !!sessionToken;
+
+ // Redirect logged-in users away from login page
+ if (isAuthPage && isLoggedIn) {
+ return NextResponse.redirect(new URL("/dashboard", request.url));
+ }
+
+ // Redirect unauthenticated users to login
+ if (!isAuthPage && !isLoggedIn) {
+ return NextResponse.redirect(new URL("/login", request.url));
+ }
+
+ return NextResponse.next();
+}
+
+export const config = {
+ matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
+};
diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts
new file mode 100644
index 0000000..17d06af
--- /dev/null
+++ b/src/types/next-auth.d.ts
@@ -0,0 +1,14 @@
+import type { Role } from "@/generated/prisma/client";
+
+declare module "next-auth" {
+ interface Session {
+ user: {
+ id: string;
+ name?: string | null;
+ email?: string | null;
+ image?: string | null;
+ role: Role;
+ organizationId: string | null;
+ };
+ }
+}