@auth/solid-start
Version:
Authentication for SolidStart.
285 lines (270 loc) • 7.15 kB
text/typescript
/**
*
* :::warning
* `@auth/solid-start` is currently experimental. The API _will_ change in the future.
* :::
*
* SolidStart Auth is the official SolidStart integration for Auth.js.
* It provides a simple way to add authentication to your SolidStart app in a few lines of code.
*
* ## Installation
*
* ```bash npm2yarn
* npm install @auth/core @auth/solid-start
* ```
*
* @module @auth/solid-start
*/
import { Auth, type AuthConfig, customFetch } from "@auth/core"
import type { AuthAction, Session } from "@auth/core/types"
export { customFetch }
export { AuthError, CredentialsSignin } from "@auth/core/errors"
export type {
Account,
DefaultSession,
Profile,
Session,
User,
} from "@auth/core/types"
export interface SolidAuthConfig extends AuthConfig {
/**
* Defines the base path for the auth routes.
* @default '/api/auth'
*/
prefix?: string
}
const actions: AuthAction[] = [
"providers",
"session",
"csrf",
"signin",
"signout",
"callback",
"verify-request",
"error",
]
function SolidAuthHandler(prefix: string, authOptions: SolidAuthConfig) {
return async (event: any) => {
const { request } = event
const url = new URL(request.url)
const action = url.pathname
.slice(prefix.length + 1)
.split("/")[0] as AuthAction
if (!actions.includes(action) || !url.pathname.startsWith(prefix + "/")) {
return
}
return await Auth(request, authOptions)
}
}
/**
* ## Setup
*
* [Generate an auth secret](https://generate-secret.vercel.app/32), then set it as an environment variable:
*
* ```
* AUTH_SECRET=your_auth_secret
* ```
*
* ## Creating the API handler
*
* This example uses github, make sure to set the following environment variables:
*
* ```
* GITHUB_ID=your_github_oauth_id
* GITHUB_SECRET=your_github_oauth_secret
* ```
*
* ```ts
* // routes/api/auth/[...solidauth].ts
* import { SolidAuth, type SolidAuthConfig } from "@auth/solid-start"
* import GitHub from "@auth/core/providers/github"
*
* export const authOpts: SolidAuthConfig = {
* providers: [
* GitHub({
* clientId: process.env.GITHUB_ID,
* clientSecret: process.env.GITHUB_SECRET,
* }),
* ],
* debug: false,
* }
*
* export const { GET, POST } = SolidAuth(authOpts)
* ```
*
* ## Getting the current session
*
* ```ts
* import { getSession } from "@auth/solid-start"
* import { createServerData$ } from "solid-start/server"
* import { authOpts } from "~/routes/api/auth/[...solidauth]"
*
* export const useSession = () => {
* return createServerData$(
* async (_, { request }) => {
* return await getSession(request, authOpts)
* },
* { key: () => ["auth_user"] }
* )
* }
*
* // useSession returns a resource:
* const session = useSession()
* const loading = session.loading
* const user = () => session()?.user
* ```
* ## Protected Routes
*
* ### When Using SSR
*
* When using SSR, it is recommended to create a `Protected` component that will trigger suspense using the `Show` component. It should look like this:
*
*
* ```tsx
* // components/Protected.tsx
* import { type Session } from "@auth/core/types";
* import { getSession } from "@auth/solid-start";
* import { Component, Show } from "solid-js";
* import { useRouteData } from "solid-start";
* import { createServerData$, redirect } from "solid-start/server";
* import { authOpts } from "~/routes/api/auth/[...solidauth]";
*
* const Protected = (Comp: IProtectedComponent) => {
* const routeData = () => {
* return createServerData$(
* async (_, event) => {
* const session = await getSession(event.request, authOpts);
* if (!session || !session.user) {
* throw redirect("/");
* }
* return session;
* },
* { key: () => ["auth_user"] }
* );
* };
*
* return {
* routeData,
* Page: () => {
* const session = useRouteData<typeof routeData>();
* return (
* <Show when={session()} keyed>
* {(sess) => <Comp {...sess} />}
* </Show>
* );
* },
* };
* };
*
* type IProtectedComponent = Component<Session>;
*
* export default Protected;
* ```
*
* It can be used like this:
*
*
* ```tsx
* // routes/protected.tsx
* import Protected from "~/components/Protected";
*
* export const { routeData, Page } = Protected((session) => {
* return (
* <main class="flex flex-col gap-2 items-center">
* <h1>This is a protected route</h1>
* </main>
* );
* });
*
* export default Page;
* ```
*
* ### When Using CSR
*
* When using CSR, the `Protected` component will not work as expected and will cause the screen to flash. To fix this, a Solid-Start middleware is used:
*
* ```tsx
* // entry-server.tsx
* import { Session } from "@auth/core";
* import { getSession } from "@auth/solid-start";
* import { redirect } from "solid-start";
* import {
* StartServer,
* createHandler,
* renderAsync,
* } from "solid-start/entry-server";
* import { authOpts } from "./routes/api/auth/[...solidauth]";
*
* const protectedPaths = ["/protected"]; // add any route you wish in here
*
* export default createHandler(
* ({ forward }) => {
* return async (event) => {
* if (protectedPaths.includes(new URL(event.request.url).pathname)) {
* const session = await getSession(event.request, authOpts);
* if (!session) {
* return redirect("/");
* }
* }
* return forward(event);
* };
* },
* renderAsync((event) => <StartServer event={event} />)
* );
* ```
*
* And now a protected route can be created:
*
*
* ```tsx
* // routes/protected.tsx
* export default () => {
* return (
* <main class="flex flex-col gap-2 items-center">
* <h1>This is a protected route</h1>
* </main>
* );
* };
* ```
*
* :::note
* The CSR method should also work when using SSR, the SSR method shouldn't work when using CSR
* :::
*
*/
export function SolidAuth(config: SolidAuthConfig) {
const { 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"
)
const handler = SolidAuthHandler(prefix, authOptions)
return {
async GET(event: any) {
return await handler(event)
},
async POST(event: any) {
return await handler(event)
},
}
}
export type GetSessionResult = Promise<Session | null>
export async function getSession(
req: Request,
options: Omit<AuthConfig, "raw">
): GetSessionResult {
options.secret ??= process.env.AUTH_SECRET
options.trustHost ??= true
const url = new URL("/api/auth/session", req.url)
const response = await Auth(
new Request(url, { headers: req.headers }),
options
)
const { status = 200 } = response
const data = await response.json()
if (!data || !Object.keys(data).length) return null
if (status === 200) return data
throw new Error(data.message)
}