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..7eff3a50 --- /dev/null +++ b/integration/react-router.mdx @@ -0,0 +1,372 @@ +--- +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. Enable Middleware Feature Flag + +First, enable the middleware feature in your React Router config. Create or update `react-router.config.ts`: + +```ts react-router.config.ts +import type { Config } from "@react-router/dev/config"; + +export default { + future: { + unstable_middleware: true, + }, +} satisfies Config; +``` + +**Important**: Middleware is currently experimental in React Router 7 and requires this feature flag to be enabled. + +### 2. Set Up Authentication Middleware + +Configure authentication middleware to handle auth centrally, similar to Next.js middleware. This approach is more efficient as it runs once per request before any loaders. + +Update `app/root.tsx`: + +```tsx app/root.tsx +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; +import type { LinksFunction, Route } from "react-router"; +import "./tailwind.css"; + +import { authMiddleware, userContext } from "@civic/auth/react-router-7"; + +export const unstable_middleware = [ + authMiddleware({ + clientId: "YOUR_CLIENT_ID", + }), +]; + +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 function loader({ context }: Route.LoaderArgs) { + const user = context.get(userContext); // Guaranteed to exist + return { user }; +} + +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 default function Index() { + 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. Protected routes can access user data directly from context: + +### Creating a Protected Route + +Create a protected route that gets user data from context: + +```tsx app/routes/protected._index.tsx +import { data, redirect, Outlet } from "react-router"; +import type { Route, MetaFunction } from "react-router"; +import { userContext } from "@civic/auth/react-router-7"; + +export const meta: MetaFunction = () => { + return [{ title: "Protected Page - Civic Auth Demo" }]; +}; + +export function loader({ context }: Route.LoaderArgs) { + // Get user from context (set by middleware) + const user = context.get(userContext); + + 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. **Centralized Auth**: Middleware handles authentication once per request before any loaders +2. **Context Storage**: User data is stored in React Router context for all routes to access +3. **Route Protection**: Protected route loaders get user data from context +4. **Redirect Logic**: If no user is found in context, it redirects to the home page +5. **Performance**: More efficient as authentication logic runs only once per request + +### Alternative Protection Patterns + +#### Reusable Protection Utilities + +```tsx app/utils/requireAuth.ts +import { redirect } from "react-router"; +import { userContext } from "@civic/auth/react-router-7"; + +export function requireAuth(context: any) { + const user = context.get(userContext); + + if (!user) { + throw redirect("/auth/login"); + } + + // Additional admin-specific checks can go here + if (!isUserAdmin(user.email)) { + throw redirect("/"); + } + + return user; +} +``` + +Then use it in any protected route: + +```tsx app/routes/admin.tsx +import type { Route } from "react-router"; +import { requireAuth } from "~/utils/requireAuth"; + +export function loader({ context }: Route.LoaderArgs) { + const user = requireAuth(context); + + return { user }; +} +``` + +## Benefits of Middleware Approach + +1. **Performance**: Authentication logic runs only once per request instead of in every loader +2. **Centralization**: All auth logic is centralized, similar to Next.js middleware +3. **Token Refresh**: Handles token refresh globally before any route processing +4. **Clean Separation**: Routes focus on their specific logic rather than auth concerns +5. **Consistency**: User data is always available and consistent across all routes + +## 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`: + +### Middleware Configuration + +Configure options in your `app/root.tsx` file: + +```tsx app/root.tsx +import { authMiddleware } from "@civic/auth/react-router-7"; + +export const unstable_middleware = [ + authMiddleware({ + 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 React Router 7 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 above, 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. |