react-router-authjs v0.4.2
Auth.js for React Router V7
eldevia/react-router-authjs is a seamless integration of Auth.js into React-Router, making authentication implementation in your RR applications straightforward and efficient.
Features
- Compatible with Auth.js adapters and providers.
- Effortless setup.
- Supports a wide range of authentication providers.
- Fully integrates with React Router’s progressive enhancement features.
Usage
Step 1: Create an Authenticator Service
Define a server
file (e.g., services/server.ts
) to set up and access the authenticator instance.
import { RRAuthenticator } from "react-router-authjs";
import Google from "@auth/core/providers/google";
import type { AppLoadContext } from "@react-router/cloudflare";
import PostgresAdapter from "@auth/pg-adapter";
import pg from "pg";
const { Pool } = pg;
const required = [
'DB_HOST',
'DB_USERNAME',
'DB_PASSWORD',
'DB_NAME',
'GOOGLE_CLIENT_ID',
'GOOGLE_CLIENT_SECRET'
];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(', ')}`
);
}
const pool = new Pool({
host: process.env.DB_HOST,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
let authenticator: RRAuthenticator<Record<string, unknown>>;
export const getAuthenticator = (env: Record<string, any> | AppLoadContext) => {
if (!authenticator) {
authenticator = new RRAuthenticator({
session: {
strategy: "jwt",
},
debug: env.NODE_ENV === "development",
adapter: PostgresAdapter(pool),
providers: [
Google({
clientId: env.GOOGLE_CLIENT_ID as string,
clientSecret: env.GOOGLE_CLIENT_SECRET as string,
}) as unknown as any,
],
}, env, '/auth');
}
return authenticator;
};
Step 2: Create Authentication Routes
Create a resource route (e.g., auth.$action.($providerId).tsx
) for handling authentication actions.
Note: This must be a Resource Route to ensure progressive enhancement and CSRF protection.
import type { ActionFunction, LoaderFunction } from "@react-router/node";
import { getAuthenticator } from "~/services/server";
export const loader: LoaderFunction = async ({ request, params, context }) => {
const authenticator = getAuthenticator(context.env as Record<string, string>);
return authenticator.handleAuthRoute({
request,
action: params.action!,
providerId: params.providerId,
params,
});
};
export const action: ActionFunction = async ({ request, params, context }) => {
const authenticator = getAuthenticator(context.env as Record<string, string>);
return authenticator.handleAuthRoute({
request,
action: params.action!,
providerId: params.providerId,
params,
});
};
Step 3: Environment Variables
Set the following environment variables for proper functionality:
AUTH_SECRET
: A 32-character random string for security. Generate it using:openssl rand -hex 32
AUTH_TRUST_HOST
: Set totrue
for non-Vercel deployments (e.g., Cloudflare Pages, Netlify).
Sign In, Sign Out, and User Data
Use React Router’s progressive enhancement capabilities to create authentication components that work even without JavaScript.
Example:
import { getAuthenticator } from "~/services/server";
import type { LoaderFunctionArgs } from "@react-router/cloudflare";
import { useRef } from "react";
import { useFetcher, useLoaderData } from "@react-router";
import { SignInForm, SignOutForm } from "react-router-authjs";
export const loader = async ({ request, context }: LoaderFunctionArgs) => {
const authenticator = getAuthenticator(context.env as Record<string, any>);
const providers = await authenticator.getProviders(request);
const user = await authenticator.isAuthenticated(request);
return { user, providers };
};
export default function AuthPage() {
const { user, providers } = useLoaderData<typeof loader>();
const fetcher = useFetcher();
const loading = fetcher.state === "loading" || fetcher.state === "submitting";
const signOutForm = useRef<HTMLFormElement>(null);
return (
<div>
<section className="container">
{user ? (
<div>
<h1>Welcome, {user.name}</h1>
<SignOutForm ref={signOutForm} fetcher={fetcher}>
<button disabled={loading} aria-busy={loading}>
Sign Out
</button>
</SignOutForm>
</div>
) : (
<>
{Object.entries(providers).map(([key, provider]) => (
<SignInForm fetcher={fetcher} providerId={provider.id} key={key}>
<input
type="hidden"
name="callbackUrl"
value={typeof window !== "undefined" ? window.location.href : ""}
/>
{provider.type === "email" && (
<>
<label htmlFor="email">Email address</label>
<input
type="email"
id="email"
name="email"
placeholder="Enter your email"
required
/>
</>
)}
<button disabled={loading} aria-busy={loading}>
Sign In with {provider.name}
</button>
</SignInForm>
))}
</>
)}
</section>
</div>
);
}
Callback URL
Set the callback URL for providers to:
[origin]/auth/callback/[provider]
Additional Notes
- Email Authentication: Customize the route or login page to handle email-based authentication responses (e.g., messages about email confirmation).
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago