diff --git a/docs.json b/docs.json index 0feb2bb8..2fd3390f 100644 --- a/docs.json +++ b/docs.json @@ -56,6 +56,7 @@ "pages": [ "integration/react", "integration/nextjs", + "integration/react-router", "integration/vanillajs", { "group": "Node.JS", diff --git a/images/rr7/rr_logo_dark.svg b/images/rr7/rr_logo_dark.svg new file mode 100644 index 00000000..06eace7c --- /dev/null +++ b/images/rr7/rr_logo_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/images/rr7/rr_logo_light-red.svg b/images/rr7/rr_logo_light-red.svg new file mode 100644 index 00000000..241a6447 --- /dev/null +++ b/images/rr7/rr_logo_light-red.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/images/rr7/rr_logo_light.svg b/images/rr7/rr_logo_light.svg new file mode 100644 index 00000000..9c6e1de8 --- /dev/null +++ b/images/rr7/rr_logo_light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/index.mdx b/index.mdx index e6246d1f..a551e946 100644 --- a/index.mdx +++ b/index.mdx @@ -118,6 +118,9 @@ Choose your framework for instructions on how to integrate Civic Auth into your Next.JS + + + React Router 7 Vanilla JavaScript diff --git a/integration/react-router.mdx b/integration/react-router.mdx new file mode 100644 index 00000000..1dc3fb1b --- /dev/null +++ b/integration/react-router.mdx @@ -0,0 +1,379 @@ +--- +title: "React Router 7" +icon: "/images/rr7/rr_logo_light.svg" +public: true +--- + + + **React Router 7 (formerly Remix)**: This guide covers React Router 7, which is the evolution of the Remix framework. + If you're migrating from Remix, the integration patterns are very similar. + + +## Quick Start + +Integrate Civic Auth into your React Router 7 application using the following steps (a working example is available in our [github examples repo](https://github.com/civicteam/civic-auth-examples/tree/main/packages/civic-auth/react-router)): + +**Important**: Make sure your application is using React Router version ^7.0.0 or higher. + + + This guide assumes you are using TypeScript. Please adjust the snippets as needed to remove the types if you are using + plain JS. + + +### 1. Create Auth Route Handlers + +Create a catch-all route to handle all authentication-related requests. This route will manage login, logout, callback, and user data endpoints. + +Create `app/routes/auth.$.tsx`: + +```ts app/routes/auth.$.tsx +// Import from the package with the correct path using the index.ts file +// This ensures the debug initialization in the index.ts file is executed +import { createRouteHandlers } from "@civic/auth/react-router-7"; + +// Create route handlers with app config +const { createAuthLoader, createAuthAction, getUser, getAuthData } = createRouteHandlers({ + clientId: "demo-client-1" +}); + +/** + * Catch-all loader for auth routes (GET requests) + */ +export const loader = createAuthLoader(); + +/** + * Catch-all action for auth routes (POST requests) + */ +export const action = createAuthAction(); + +/** + * Export getUser and getAuthData functions for use in other parts of the app + */ +export { getUser, getAuthData }; +``` + +### 2. Set Up Root Loader + +Configure your root route to provide user data to all routes in your application. This enables server-side rendering with authentication state. + +Update `app/root.tsx`: + +```tsx app/root.tsx +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; +import type { LinksFunction, LoaderFunction } from "react-router"; +import "./tailwind.css"; + +import { getAuthData } from "~/routes/auth.$"; + +export const links: LinksFunction = () => [ + { rel: "preconnect", href: "https://fonts.googleapis.com" }, + { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossOrigin: "anonymous", + }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", + }, +]; + +export const loader: LoaderFunction = async ({ request }) => { + const authData = await getAuthData(request); + + return { + // We bootstrap our data through the stack + ...authData, + }; +}; + +function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + + + + ); +} + +export function ErrorBoundary() { + return ( + +
+
+

Something went wrong

+

Please try refreshing the page

+
+
+
+ ); +} + +export default function App() { + return ( + + + + ); +} +``` + +## Usage + +### Getting User Information on the Frontend + +The React Router 7 integration provides several components and hooks for frontend authentication: + +#### UserButton Component + +The `UserButton` component provides a complete sign-in/sign-out experience: + +```tsx app/routes/_index.tsx +import { useLoaderData } from "react-router"; +import type { LoaderFunctionArgs, MetaFunction } from "react-router"; +import { getUser } from "~/routes/auth.$"; +import { UserButton } from "@civic/auth/react-router-7/components/UserButton"; + +export const meta: MetaFunction = () => { + return [{ title: "My App" }]; +}; + +export async function loader({ request }: LoaderFunctionArgs) { + const user = await getUser(request); + return { user }; +} + +export default function Index() { + const { user } = useLoaderData(); + + return ( +
+

My App

+ console.log("User signed in", user)} + onSignOut={() => console.log("User signed out")} + /> + + {user && ( +
+

Welcome, {user.name}!

+

Email: {user.email}

+
+ )} +
+ ); +} +``` + +#### useUser Hook + +For accessing user information in components: + +```tsx MyComponent.tsx +import { useUser } from "@civic/auth/react-router-7"; + +export function MyComponent() { + const { user, isLoggedIn } = useUser(); + + if (!isLoggedIn) { + return
Please sign in
; + } + + return
Hello {user.name}!
; +} +``` + +#### Custom Authentication Logic + +For custom sign-in buttons and authentication flows: + +```tsx CustomSignIn.tsx +import { useCallback } from "react"; +import { useUser } from "@civic/auth/react-router-7"; + +export function CustomSignIn() { + const { signIn, signOut } = useUser(); + + const doSignIn = useCallback(() => { + console.log("Starting sign-in process"); + signIn() + .then(() => { + console.log("Sign-in completed successfully"); + }) + .catch((error) => { + console.error("Sign-in failed:", error); + }); + }, [signIn]); + + const handleSignOut = async () => { + await signOut(); + }; + + return ( +
+ + +
+ ); +} +``` + +## Protected Routes + +React Router 7 supports route-level authentication using loaders. Here's how to create protected routes that require user authentication: + +### Creating a Protected Route + +Create a protected route loader that checks authentication and redirects unauthenticated users: + +```tsx app/routes/protected._index.tsx +import { data, redirect, Outlet } from "react-router"; +import type { LoaderFunctionArgs, MetaFunction } from "react-router"; +import { getUser } from "./auth.$"; + +export const meta: MetaFunction = () => { + return [{ title: "Protected Page - Civic Auth Demo" }]; +}; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const user = await getUser(request); + + if (!user) { + // Redirect to home page if user is not authenticated + return redirect("/"); + } + + return data({ user }); +}; + +export default function ProtectedPage() { + return ( +
+ +
+ ); +} +``` + +### Protected Route Layout + +Create the protected route content that displays to authenticated users: + +```tsx app/routes/protected.tsx +import { Link } from "react-router"; +import type { MetaFunction } from "react-router"; +import { useUser } from "@civic/auth/react-router-7/useUser"; + +export const meta: MetaFunction = () => { + return [{ title: "Protected Page - Civic Auth Demo" }]; +}; + +export default function ProtectedLayout() { + const { user } = useUser(); + + return ( +
+
+

Protected Page

+ +
+

Welcome to this protected page, {user?.name || "User"}!

+

This content is only visible to authenticated users.

+
+ +
+ + Back to Home + + + Logout + +
+
+
+ ); +} +``` + +### How It Works + +1. **Route Protection**: The `protected._index.tsx` loader runs on the server before rendering the page +2. **Authentication Check**: It uses `getUser(request)` to check if the user is authenticated +3. **Redirect Logic**: If no user is found, it redirects to the home page using `redirect("/")` +4. **User Data**: If authenticated, it passes the user data to the route +5. **Protected Content**: The `protected.tsx` layout displays content only to authenticated users + +### Alternative Protection Patterns + +You can also create a reusable protection utility: + +```tsx app/utils/requireAuth.ts +import { redirect } from "react-router"; +import { getUser } from "~/routes/auth.$"; + +export async function requireAuth(request: Request) { + const user = await getUser(request); + + if (!user) { + throw redirect("/auth/login"); + } + + // Additional admin-specific checks can go here + // Example: Check if user has admin privileges (implement according to your needs) + if (!isUserAdmin(user.email)) { + throw redirect("/"); + } + + return user; +} +``` + +Then use it in any protected route: + +```tsx app/routes/admin.tsx +import type { LoaderFunctionArgs } from "react-router"; +import { requireAuth } from "~/utils/requireAuth"; + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const user = await requireAuth(request); + + return { user }; +}; +``` + +## Advanced Configuration + +Civic Auth is a "low-code" solution, so most configuration takes place via the [dashboard](https://auth.civic.com). Changes you make there will be updated automatically in your integration without any code changes. + +You can customize the library according to your React Router 7 app's needs. Configure options when calling `createRouteHandlers`: + +```ts app/routes/auth.$.tsx +import { createRouteHandlers } from "@civic/auth/react-router-7"; + +const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: "YOUR_CLIENT_ID", + loginSuccessUrl: "/myCustomSuccessEndpoint", + callbackUrl: "/api/myroute/callback", + logoutUrl: "/goodbye", + baseUrl: "https://myapp.com", +}); +``` + +### Configuration Options + +| Field | Required | Default | Example | Description | +| ----------------- | -------- | ---------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `clientId` | Yes | - | `2cc5633d-2c92-48da-86aa-449634f274b9` | The key obtained on signup to [auth.civic.com](https://auth.civic.com/) | +| `loginSuccessUrl` | No | - | `/myCustomSuccessEndpoint` | In a NextJS app, we will redirect your user to this page once the login is finished. If not set, users will be sent back to the root of your app. | +| `callbackUrl` | No | `/auth/callback` | `/api/myroute/callback` | If you cannot host Civic’s SDK handlers in the default location, you can specify a custom callback route here. This is where you must attach Civic’s GET handler as described [here](/integration/nextjs#2-create-the-civic-auth-api-route), so Civic can complete the OAuth token exchange. Use `loginSuccessUrl` to redirect after login. | +| `logoutUrl` | No | `/` | `/goodbye` | The path your user will be sent to after a successful log-out. | +| `baseUrl` | No | - | `https://myapp.com` | The public-facing base URL for your application. Required when deploying behind reverse proxies (Cloudfront + Vercel, AWS ALB, nginx, etc.) to ensure authentication redirects use the correct public domain instead of internal origins. |