Skip to content

Instantly share code, notes, and snippets.

@lawrencecchen
Last active July 10, 2024 07:32
Show Gist options
  • Save lawrencecchen/75fee3be74a8cc56aedae66b17d6d83c to your computer and use it in GitHub Desktop.
Save lawrencecchen/75fee3be74a8cc56aedae66b17d6d83c to your computer and use it in GitHub Desktop.
next-auth with remix
// app/routes/api/auth/$.ts
import NextAuth from "~/lib/next-auth/index.server";
export const { action, loader } = NextAuth({
providers: [
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
}),
],
secret: env.NEXTAUTH_SECRET,
});
// 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<any>) {
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<T extends Session>(
options: NextAuthOptions
) {
async function getServerSession(request: Request): Promise<T | null> {
const session = await NextAuthHandler<T>({
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<Session> {
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;
}
@jeeyoungk
Copy link

This is quite a cute implementation to wrap NextJS with Remix! 👏 Taking some ideas from this one....

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