// app/lib/next-auth/index.server.ts import { DataFunctionArgs, json, redirect } from "@remix-run/node"; import cookie from "cookie"; import { IncomingRequest, NextAuthOptions, Session } from "next-auth"; import type { OutgoingResponse } from "next-auth/core"; import { NextAuthHandler } from "next-auth/core"; import type { NextAuthAction } from "next-auth/lib/types"; import invariant from "tiny-invariant"; import { nextAuthOptions } from "./options.server"; async function toRemixResponse(nextAuthResponse: OutgoingResponse) { const { headers: nextAuthHeaders, cookies, body: nextAuthBody, redirect: nextAuthRedirect, status = 200, } = nextAuthResponse; const headers = new Headers(); nextAuthHeaders?.forEach((header) => { headers.append(header.key, header.value); }); for (const item of cookies ?? []) { headers.append( "Set-Cookie", cookie.serialize(item.name, item.value, item.options) ); } if (nextAuthRedirect) { if (!nextAuthBody) { throw redirect(nextAuthRedirect, { status: 302, headers, }); } else { return json({ url: nextAuthRedirect, headers }); } } if (headers.get("Content-Type") === "application/json") { return json(nextAuthBody, { status, headers }); } return new Response(nextAuthBody, { status, headers }); } const NEXTAUTH_URL = process.env.VERCEL_URL ?? process.env.NEXTAUTH_URL; async function RemixNextAuthHandler( { request, params }: DataFunctionArgs, options: NextAuthOptions ) { const url = new URL(request.url); invariant(params["*"], "nextauth is required"); const nextauth = params["*"].split("/"); let body = {}; try { body = Object.fromEntries(await request.formData()); } catch { // no formData passed } const req: IncomingRequest = { host: NEXTAUTH_URL, body, query: Object.fromEntries(url.searchParams), headers: request.headers, method: request.method, cookies: cookie.parse(request.headers.get("cookie") ?? ""), action: nextauth[0] as NextAuthAction, providerId: nextauth?.[1], error: nextauth?.[1], }; const response = await NextAuthHandler({ req, options, }); return toRemixResponse(response); } export default function NextAuth(options: NextAuthOptions) { return { loader: (args: DataFunctionArgs) => RemixNextAuthHandler(args, options), action: (args: DataFunctionArgs) => RemixNextAuthHandler(args, options), }; } export function createGetServerSession( options: NextAuthOptions ) { async function getServerSession(request: Request): Promise { const session = await NextAuthHandler({ req: { host: NEXTAUTH_URL, action: "session", method: "GET", cookies: cookie.parse(request.headers.get("cookie") ?? ""), headers: request.headers, }, options, }); const { body } = session; if (body && Object.keys(body).length) return body as T; return null; } return getServerSession; } export const getServerSession = createGetServerSession(nextAuthOptions); export function getCurrentPath(request: Request) { return new URL(request.url).pathname; } export function makeRedirectToFromHere(request: Request) { return new URLSearchParams([["callbackUrl", getCurrentPath(request)]]); } export async function requireAuthSession( request: Request, options: { onFailRedirectTo?: string; } = {} ): Promise { const session = await getServerSession(request); if (!session) { if (options.onFailRedirectTo) { throw redirect( `${options.onFailRedirectTo}?${makeRedirectToFromHere(request)}` ); } throw redirect(`/api/auth/signin?${makeRedirectToFromHere(request)}`); } return session; }