fix(auth): replace client-side signin page with server-side route handler

The client page rendered inside AppShell layout, causing a flash of the
full app UI before redirecting to Authentik. The new route handler
initiates the OAuth flow server-side (CSRF token + POST to NextAuth
provider signin) and redirects instantly — no visible page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-09 13:16:45 +02:00
parent acfec5abe5
commit 179dc306bb
2 changed files with 74 additions and 29 deletions
-29
View File
@@ -1,29 +0,0 @@
"use client";
import { signIn } from "next-auth/react";
import { useSearchParams } from "next/navigation";
import { useEffect } from "react";
/**
* Custom sign-in page that auto-redirects to Authentik.
* Skips the default NextAuth provider chooser (no "Sign in with Authentik" button).
*/
export default function SignInPage() {
const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl") || "/";
useEffect(() => {
void signIn("authentik", { callbackUrl });
}, [callbackUrl]);
return (
<div className="flex h-screen items-center justify-center bg-background">
<div className="flex flex-col items-center gap-4">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<p className="text-sm text-muted-foreground">
Se redirecționează către autentificare...
</p>
</div>
</div>
);
}
+74
View File
@@ -0,0 +1,74 @@
import { NextRequest, NextResponse } from "next/server";
/**
* Server-side signin route that initiates OAuth flow with Authentik.
* Fetches CSRF token from NextAuth, POSTs to provider signin,
* and redirects to Authentik's authorize URL — no visible page.
*/
export async function GET(request: NextRequest) {
const callbackUrl =
request.nextUrl.searchParams.get("callbackUrl") || "/";
const baseUrl = process.env.NEXTAUTH_URL || "https://tools.beletage.ro";
try {
// Use internal URL for server-to-server calls (avoid external roundtrip)
const internalBase = `http://127.0.0.1:${process.env.PORT || "3000"}`;
// Step 1: Get CSRF token from NextAuth
const csrfRes = await fetch(`${internalBase}/api/auth/csrf`, {
headers: { cookie: request.headers.get("cookie") || "" },
});
const csrfData = (await csrfRes.json()) as { csrfToken: string };
const csrfToken = csrfData.csrfToken;
// Merge request cookies with new CSRF cookies
const csrfSetCookies = csrfRes.headers.getSetCookie();
const existingCookies = request.headers.get("cookie") || "";
const newCookiePairs = csrfSetCookies
.map((c) => c.split(";")[0])
.filter(Boolean);
const mergedCookie = [existingCookies, ...newCookiePairs]
.filter(Boolean)
.join("; ");
// Step 2: POST to NextAuth's provider-specific signin
const signinRes = await fetch(
`${internalBase}/api/auth/signin/authentik`,
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
cookie: mergedCookie,
},
body: new URLSearchParams({ csrfToken, callbackUrl }).toString(),
redirect: "manual",
},
);
// Step 3: Get the redirect URL (Authentik's authorize endpoint)
const location = signinRes.headers.get("location");
if (location) {
// Build absolute URL if relative
const redirectUrl = location.startsWith("http")
? location
: `${baseUrl}${location}`;
const response = NextResponse.redirect(redirectUrl);
// Forward all set-cookie headers to browser (CSRF token, state, etc.)
for (const cookie of csrfSetCookies) {
response.headers.append("set-cookie", cookie);
}
for (const cookie of signinRes.headers.getSetCookie()) {
response.headers.append("set-cookie", cookie);
}
return response;
}
} catch (error) {
console.error("[auth/signin] Server-side redirect failed:", error);
}
// Fallback: redirect to NextAuth's built-in signin page
return NextResponse.redirect(
`${baseUrl}/api/auth/signin?callbackUrl=${encodeURIComponent(callbackUrl)}`,
);
}