Skip to content

Instantly share code, notes, and snippets.

@leog
Last active September 13, 2024 12:24
Show Gist options
  • Save leog/8f713ba75e83d1ddd220455e3a89ee0c to your computer and use it in GitHub Desktop.
Save leog/8f713ba75e83d1ddd220455e3a89ee0c to your computer and use it in GitHub Desktop.
Next-Auth Server-side Sign in
import { AuthHandler } from "node_modules/next-auth/core";
import { getServerAuthSession } from "@/server/auth";
import { cookies, headers } from "next/headers";
import { getCsrfToken } from "next-auth/react";
import { authOptions } from "@/server/auth";
import { type AuthAction } from "next-auth";
import { redirect } from "next/navigation";
import { createHash } from "crypto";
export default async function Page({
searchParams,
}: {
searchParams?: Record<string, string | string[] | undefined>;
}) {
// check existing session to avoid redirecting to signin unnecessarily
const session = await getServerAuthSession();
if (session && req.url) {
redirect(req.url);
}
// getting cookies' name in case we are in a secure environment
const csrfTokenCookieName =
authOptions.cookies?.csrfToken?.name ?? "next-auth.csrf-token";
const callbackUrlCookieName =
authOptions.cookies?.callbackUrl?.name ?? "next-auth.callback-url";
// csrf token needed to authenticate, extracted or generated on demand if needed
let csrfToken = cookies().get(csrfTokenCookieName)?.value;
if (!csrfToken) {
const res = await AuthHandler({
req: {
method: "GET",
action: "csrf" as AuthAction,
providerId: "provider-id", // replace with yours
error: "provider-id", // replace with yours
},
options: {
...authOptions,
},
});
csrfToken = (res.body as { csrfToken: string }).csrfToken;
}
// consolidating cookies and their values to pass along to generate auth URL
const allCookies = cookies().getAll();
const csrfTokenHash = createHash("sha256")
.update(`${csrfToken}${process.env.NEXTAUTH_SECRET}`)
.digest("hex");
const cookiesObject = {
...{
[csrfTokenCookieName]: `${csrfToken}|${csrfTokenHash}`,
[callbackUrlCookieName]: callbackFullUrl.href,
},
...Object.fromEntries(allCookies.map((c) => [c.name, c.value])),
};
const data = {
req: {
body: {
csrfToken: csrfToken?.split("|")[0],
callbackUrl,
json: "true",
},
method: "POST",
cookies: cookiesObject,
headers: Object.fromEntries(headers()),
action: "signin" as AuthAction,
providerId: "provider-id", // replace with yours
error: "provider-id", // replace with yours
},
options: authOptions,
};
const res = await AuthHandler(data);
redirect(res.redirect ?? "/");
}
@kentmichael
Copy link

I test this and the value of res is { redirect: 'http://localhost:3230/api/auth/signin', cookies: [] }.

@leog
Copy link
Author

leog commented Sep 5, 2024

I test this and the value of res is { redirect: 'http://localhost:3230/api/auth/signin', cookies: [] }.

@kentmichael that might be because csrf wasn't there, updated it now, test it again

@kentmichael
Copy link

Still giving me the same result.. Im trying to implement a server side signin using nextauth the closest i've been to is this one, nextauthjs/next-auth#45 (comment). This sends a post request to /api/auth/callback/credentials, successfully hit the authorize method in the credentials provider and called the JWT callback, but the session callback is not firing, thus not saving the session.

@M3LiNdRu
Copy link

M3LiNdRu commented Sep 13, 2024

The following piece of code works for me, however with some drawbacks:

import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
import { cookies } from "next/headers"
import { auth } from "../../../auth"
import { createHash } from "crypto";

export async function GET(request: NextRequest) {
     // check existing session to avoid redirecting to signin unnecessarily
    const { searchParams } = request.nextUrl;
    const referer = searchParams.get('redirect_uri'); 
    const session = await auth();
    if (session) return NextResponse.redirect(referer ?? '/');
        
    // getting cookies name in case we are in a secure environment
    const cookieStore = cookies()
    let csrfToken = cookieStore.get("authjs.csrf-token")?.value;
    const callbackUrl = referer //cookieStore.get("auth.callback-url")?.value;

    if (!csrfToken) {
        const csrfApiResponse = await fetch(`${process.env.AUTH_URL}/api/auth/csrf`).then((res) => res.json())
        console.log(`CsrfToken fetched: ${csrfApiResponse.csrfToken}`)
        csrfToken = csrfApiResponse.csrfToken
    }

    //Calculate csrfTokenHash
    const csrfTokenHash = createHash("sha256")
        .update(`${csrfToken}${process.env.AUTH_SECRET}`)
        .digest("hex");

    //Custom POST signin request
    const fetchRequest: RequestInit = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Cookie': `authjs.csrf-token=${csrfToken}|${csrfTokenHash};auth.callback-url=${callbackUrl}`
        },
        body: JSON.stringify({
            csrfToken: csrfToken?.split("|")[0],
            callbackUrl
        }),
        credentials: 'same-origin'
    };
    const fetchSignIn = await fetch(`${process.env.AUTH_URL}/api/auth/signin/[YOUR_PROVIDER]`, fetchRequest);

    return NextResponse.redirect(fetchSignIn.url ?? "/");
}

The drawbacks are:

  • checks must set to none
  • redirect_uri it seems it does not work properly

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