Skip to content

Instantly share code, notes, and snippets.

@sergiodxa
Created January 4, 2023 17:37
Show Gist options
  • Save sergiodxa/030468445814cb18d9e49d8518c6ab31 to your computer and use it in GitHub Desktop.
Save sergiodxa/030468445814cb18d9e49d8518c6ab31 to your computer and use it in GitHub Desktop.
import Auth0 from "@auth/core/providers/auth0";
import { RemixAuth } from "~/services/auth"
import { sessionStorage } from "~/services/session.server";
export let { loader, action } = RemixAuth({
sessionStorage: sessionStorage, // this does nothing yet
secret: process.env.AUTH_SECRET ?? "s3cr3t",
providers: [
Auth0({
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
issuer: process.env.AUTH0_ISSUER_BASE_URL,
}),
],
});
import type { AuthAction, AuthConfig, Session } from "@auth/core/types";
import type {
ActionFunction,
LoaderFunction,
SessionStorage,
} from "@remix-run/node";
import type { Cookie } from "set-cookie-parser";
import { Auth } from "@auth/core";
import { serialize } from "cookie";
import { parseString, splitCookiesString } from "set-cookie-parser";
global.crypto = require("crypto"); // hack required because Auth.js uses global.crypto even in Node
export interface RemixAuthConfig extends AuthConfig {
/**
* Defines the base path for the auth routes.
* @default '/api/auth'
*/
prefix?: string;
sessionStorage: SessionStorage;
}
const actions: Set<AuthAction> = new Set([
"providers",
"session",
"csrf",
"signin",
"signout",
"callback",
"verify-request",
"error",
]);
// Copied from Solid implementation, ideally we use SessionStorage
const getSetCookieCallback = (cook?: string | null): Cookie | undefined => {
if (!cook) return;
let splitCookie = splitCookiesString(cook);
for (let cookName of [
"__Secure-next-auth.session-token",
"next-auth.session-token",
"next-auth.pkce.code_verifier",
"__Secure-next-auth.pkce.code_verifier",
]) {
let temp = splitCookie.find((e) => e.startsWith(`${cookName}=`));
if (temp) {
return parseString(temp);
}
}
return parseString(splitCookie?.[0] ?? ""); // just return the first cookie if no session token is found
};
function RemixAuthHandler(
prefix: string,
authOptions: RemixAuthConfig
): LoaderFunction | ActionFunction {
return async ({ request, params }) => {
let { pathname } = new URL(request.url);
let action = params["*"].split("/").at(0) as AuthAction;
if (!actions.has(action) || !pathname.startsWith(prefix + "/")) {
return null;
}
let res = await Auth(request, authOptions);
if (["callback", "signin", "signout"].includes(action)) {
let parsedCookie = getSetCookieCallback(res.headers.get("Set-Cookie"));
if (parsedCookie) {
res.headers.set(
"Set-Cookie",
serialize(parsedCookie.name, parsedCookie.value, parsedCookie as any)
);
}
}
return res;
};
}
export function RemixAuth(config: RemixAuthConfig) {
let { prefix = "/api/auth", ...authOptions } = config;
authOptions.secret ??= process.env.AUTH_SECRET;
authOptions.trustHost ??= !!(
process.env.AUTH_TRUST_HOST ??
process.env.VERCEL ??
process.env.NODE_ENV !== "production"
);
let handler = RemixAuthHandler(prefix, authOptions);
let result: { loader: LoaderFunction; action: ActionFunction } = {
async loader(args) {
return handler(args);
},
async action(args) {
return handler(args);
},
};
return result;
}
export type GetSessionResult = Promise<Session | null>;
export async function getSession(
req: Request,
options: AuthConfig
): GetSessionResult {
options.secret ??= process.env.AUTH_SECRET;
options.trustHost ??= true;
let url = new URL("/api/auth/session", req.url);
let response = await Auth(
new Request(url, { headers: req.headers }),
options
);
let { status = 200 } = response;
let data = await response.json();
if (!data || Object.keys(data).length === 0) return null;
if (status === 200) return data as Session;
throw new Error((data as { message: string }).message);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment