From 728fc71c8af1de1f00d7dec25013a92134ab7ca9 Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Wed, 9 Jul 2025 09:50:11 +0200 Subject: [PATCH 1/8] docs(rr7): First version of RR7 --- docs.json | 1 + index.mdx | 3 + integration/react-router.mdx | 578 +++++++++++++++++++++++++++++++++++ 3 files changed, 582 insertions(+) create mode 100644 integration/react-router.mdx diff --git a/docs.json b/docs.json index e9d853ac..189631ac 100644 --- a/docs.json +++ b/docs.json @@ -49,6 +49,7 @@ "group": "Integration", "pages": [ "integration/react", + "integration/react-router", "integration/nextjs", "integration/vanillajs", { diff --git a/index.mdx b/index.mdx index 333f4649..6813547a 100644 --- a/index.mdx +++ b/index.mdx @@ -116,6 +116,9 @@ Choose your framework for instructions on how to integrate Civic Auth into your React + + React Router 7 + Next.JS diff --git a/integration/react-router.mdx b/integration/react-router.mdx new file mode 100644 index 00000000..7275d685 --- /dev/null +++ b/integration/react-router.mdx @@ -0,0 +1,578 @@ +--- +title: "React Router 7" +icon: "js" +public: true +--- + +## 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 is specifically + designed for React Router 7 (formerly Remix). + + + + This guide assumes you are using TypeScript. Please adjust the snippets as needed to remove the types if you are using + plain JS. + + +If you plan to use Web3 features, select "Auth + Web3" from the tabs below. + +### 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 { createRouteHandlers } from "@civic/auth/remix"; + + // Create route handlers with your configuration + const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: process.env.CLIENT_ID, + }); + + /** + * Catch-all loader for auth routes + */ + export const loader = createAuthLoader(); + + /** + * Export getUser function for use in other parts of the app + */ + export { getUser }; + ``` + + + + ```ts app/routes/auth.$.tsx + import { createRouteHandlers } from "@civic/auth-web3/remix"; + + // Create route handlers with your configuration + const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: process.env.CLIENT_ID, + oauthServer: process.env.OAUTH_SERVER, + callbackUrl: process.env.CALLBACK_URL || "http://localhost:5173/auth/callback", + loginUrl: process.env.LOGIN_URL || "/auth/login", + logoutUrl: process.env.LOGOUT_URL || "/auth/logout", + refreshUrl: process.env.REFRESH_URL || "/auth/refresh", + logoutCallbackUrl: process.env.LOGOUT_CALLBACK_URL || "/", + scope: process.env.SCOPE || "openid profile email", + }); + + /** + * Catch-all loader for auth routes + */ + export const loader = createAuthLoader(); + + /** + * Export getUser function for use in other parts of the app + */ + export { getUser }; + ``` + + + + +### 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"; // Your CSS imports + + import { getUser } from "~/routes/auth.$"; + + export const links: LinksFunction = () => [ + // Your link definitions + ]; + + export const loader: LoaderFunction = async ({ request }) => { + const user = await getUser(request); + return { + // You need this for the useUser hook to work + user, + }; + }; + + function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + ``` + + + + ```tsx app/root.tsx + import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; + import type { LinksFunction, LoaderFunction } from "react-router"; + import "./tailwind.css"; // Your CSS imports + + import { getUser } from "~/routes/auth.$"; + + export const links: LinksFunction = () => [ + // Your link definitions + ]; + + export const loader: LoaderFunction = async ({ request }) => { + const user = await getUser(request); + return { + // You need this for the useUser hook to work + user, + }; + }; + + function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + + + + ); + } + + export default function App() { + return ( + + + + ); + } + ``` + + + + +### 3. Protecting Routes + +To protect routes that require authentication, check for user data in your route loaders and redirect unauthenticated users. + +Example protected route `app/routes/protected.tsx`: + + + + ```tsx app/routes/protected.tsx + import { data, redirect } from "react-router"; + import type { LoaderFunctionArgs, MetaFunction } from "react-router"; + import { getUser } from "./auth.$"; + + export const meta: MetaFunction = () => { + return [{ title: "Protected Page" }]; + }; + + export const loader = async ({ request }: LoaderFunctionArgs) => { + const user = await getUser(request); + + if (!user) { + // Redirect to home or login page + return redirect("/"); + } + + return data({ user }); + }; + + export default function ProtectedPage() { + return ( +
+

Protected Content

+

This page requires authentication to access.

+
+ ); + } + ``` + +
+ + ```tsx app/routes/protected.tsx + import { data, redirect } from "react-router"; + import type { LoaderFunctionArgs, MetaFunction } from "react-router"; + import { getUser } from "./auth.$"; + + export const meta: MetaFunction = () => { + return [{ title: "Protected Page" }]; + }; + + export const loader = async ({ request }: LoaderFunctionArgs) => { + const user = await getUser(request); + + if (!user) { + // Redirect to home or login page + return redirect("/"); + } + + return data({ user }); + }; + + export default function ProtectedPage() { + return ( +
+

Protected Content

+

This page requires authentication to access.

+
+ ); + } + ``` + +
+
+ +## 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/remix/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}

+
+ )} +
+ ); + } + ``` + +
+ + ```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-web3/remix/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/remix"; + + export function MyComponent() { + const { user, isLoggedIn } = useUser(); + + if (!isLoggedIn) { + return
Please sign in
; + } + + return
Hello {user.name}!
; + } + ``` + +
+ + ```tsx MyComponent.tsx + import { useUser } from "@civic/auth-web3/remix"; + + 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 { startFrontendAuth, logout } from "@civic/auth/remix"; + + export function CustomSignIn() { + const handleSignIn = async () => { + try { + await startFrontendAuth({ + clientId: "YOUR_CLIENT_ID", + displayMode: "iframe", // or "redirect", "new_tab", "embedded" + }); + // Page will revalidate automatically after successful auth + } catch (error) { + console.error("Sign-in failed:", error); + } + }; + + const handleSignOut = async () => { + await logout(); + }; + + return ( +
+ + +
+ ); + } + ``` + +
+ + ```tsx CustomSignIn.tsx + import { useUser } from "@civic/auth/remix"; + + 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 logout(); + }; + + return ( +
+ + +
+ ); + } + ``` + +
+
+ +## 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/remix"; + + const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: "YOUR_CLIENT_ID", + oauthServer: "https://auth.civic.com/oauth", + callbackUrl: "https://myapp.com/auth/callback", + loginUrl: "/auth/login", + logoutUrl: "/auth/logout", + refreshUrl: "/auth/refresh", + logoutCallbackUrl: "/goodbye", + scope: "openid profile email", + include: ["/admin/*", "/dashboard/*"], // Routes requiring auth + exclude: ["/public/*", "/api/health"], // Public routes + + // Cookie configuration + cookies: { + tokens: { + secure: true, + httpOnly: true, + sameSite: "none", // For iframe compatibility + maxAge: 3600, // 1 hour + }, + user: { + secure: true, + httpOnly: false, + sameSite: "lax", + maxAge: 3600, + }, + }, + + // Frontend integration options + frontend: { + enabled: true, + defaultDisplayMode: "iframe", + authProcessTimeout: 60000, + corsOrigins: ["http://localhost:3000", "https://myapp.com"], + iframeCompatibleCookies: true, + }, + + // Logging configuration + logging: { + enableFor: "@civic/auth:*", + }, + }); + ``` + + + + ```ts app/routes/auth.$.tsx + import { createRouteHandlers } from "@civic/auth-web3/remix"; + + const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: "YOUR_CLIENT_ID", + oauthServer: "https://auth.civic.com/oauth", + callbackUrl: "https://myapp.com/auth/callback", + loginUrl: "/auth/login", + logoutUrl: "/auth/logout", + refreshUrl: "/auth/refresh", + logoutCallbackUrl: "/goodbye", + scope: "openid profile email", + include: ["/admin/*", "/dashboard/*"], // Routes requiring auth + exclude: ["/public/*", "/api/health"], // Public routes + + // Frontend integration options + frontend: { + enabled: true, + defaultDisplayMode: "iframe", + authProcessTimeout: 60000, + corsOrigins: ["http://localhost:3000", "https://myapp.com"], + iframeCompatibleCookies: true, + }, + + // Logging configuration + logging: { + enableFor: "@civic/auth:*", + }, + }); + ``` + + + + +### 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/) | +| `oauthServer` | No | `https://auth.civic.com/oauth` | `https://auth.civic.com/oauth` | OAuth server URL | +| `callbackUrl` | Yes | - | `https://myapp.com/auth/callback` | OAuth callback URL where users return after authentication | +| `loginUrl` | No | `/auth/login` | `/auth/signin` | Login endpoint path | +| `logoutUrl` | No | `/auth/logout` | `/auth/signout` | Logout endpoint path | +| `refreshUrl` | No | `/auth/refresh` | `/auth/token/refresh` | Token refresh endpoint path | +| `logoutCallbackUrl` | No | `/` | `/goodbye` | The path users are sent to after successful logout | +| `scope` | No | `openid profile email` | `openid profile email offline_access` | OAuth scope | +| `include` | No | `["/"]` | `["/admin/*", "/dashboard/*"]` | An array of path [globs](https://man7.org/linux/man-pages/man7/glob.7.html) that require authentication | +| `exclude` | No | Auth routes | `["/public/*", "/api/health"]` | An array of path [globs](https://man7.org/linux/man-pages/man7/glob.7.html) excluded from authentication requirements | +| `cookies` | No | Secure defaults | See example above | Cookie configuration for tokens and user data | +| `frontend.enabled` | No | `true` | `false` | Enable frontend integration mode for iframe/embedded authentication | +| `frontend.defaultDisplayMode` | No | `iframe` | `redirect` | Default authentication display mode: `iframe`, `redirect`, `new_tab`, or `embedded` | +| `frontend.corsOrigins` | No | `["http://localhost:3000"]` | `["https://myapp.com"]` | CORS origins allowed for frontend integration | +| `logging.enableFor` | No | - | `@civic/auth:*` | Enable logging for debug namespaces | + From 45c4a4c054ad8daf61b37b432d8f4a5bc6aef4db Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Wed, 9 Jul 2025 12:00:28 +0200 Subject: [PATCH 2/8] docs(rr7): First RC of RR7 Docs --- docs.json | 2 +- images/rr7/rr_logo_dark.svg | 6 + images/rr7/rr_logo_light-red.svg | 6 + images/rr7/rr_logo_light.svg | 6 + index.mdx | 6 +- integration/react-router.mdx | 794 +++++++++++-------------------- integration/react.mdx | 618 ++++++++++-------------- 7 files changed, 552 insertions(+), 886 deletions(-) create mode 100644 images/rr7/rr_logo_dark.svg create mode 100644 images/rr7/rr_logo_light-red.svg create mode 100644 images/rr7/rr_logo_light.svg diff --git a/docs.json b/docs.json index 189631ac..778d116f 100644 --- a/docs.json +++ b/docs.json @@ -49,8 +49,8 @@ "group": "Integration", "pages": [ "integration/react", - "integration/react-router", "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 6813547a..fbdcbd67 100644 --- a/index.mdx +++ b/index.mdx @@ -116,11 +116,11 @@ Choose your framework for instructions on how to integrate Civic Auth into your React - - React Router 7 - Next.JS + + + React Router 7 Node.JS diff --git a/integration/react-router.mdx b/integration/react-router.mdx index 7275d685..a1c0e056 100644 --- a/integration/react-router.mdx +++ b/integration/react-router.mdx @@ -1,16 +1,19 @@ --- title: "React Router 7" -icon: "js" +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 is specifically - designed for React Router 7 (formerly Remix). + **Important**: Make sure your application is using React Router version ^7.0.0 or higher. @@ -18,65 +21,30 @@ Integrate Civic Auth into your React Router 7 application using the following st plain JS. -If you plan to use Web3 features, select "Auth + Web3" from the tabs below. - ### 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 { createRouteHandlers } from "@civic/auth/remix"; - - // Create route handlers with your configuration - const { createAuthLoader, getUser } = createRouteHandlers({ - clientId: process.env.CLIENT_ID, - }); - - /** - * Catch-all loader for auth routes - */ - export const loader = createAuthLoader(); - - /** - * Export getUser function for use in other parts of the app - */ - export { getUser }; - ``` - - - - ```ts app/routes/auth.$.tsx - import { createRouteHandlers } from "@civic/auth-web3/remix"; - - // Create route handlers with your configuration - const { createAuthLoader, getUser } = createRouteHandlers({ - clientId: process.env.CLIENT_ID, - oauthServer: process.env.OAUTH_SERVER, - callbackUrl: process.env.CALLBACK_URL || "http://localhost:5173/auth/callback", - loginUrl: process.env.LOGIN_URL || "/auth/login", - logoutUrl: process.env.LOGOUT_URL || "/auth/logout", - refreshUrl: process.env.REFRESH_URL || "/auth/refresh", - logoutCallbackUrl: process.env.LOGOUT_CALLBACK_URL || "/", - scope: process.env.SCOPE || "openid profile email", - }); - - /** - * Catch-all loader for auth routes - */ - export const loader = createAuthLoader(); - - /** - * Export getUser function for use in other parts of the app - */ - export { getUser }; - ``` - - - +```ts app/routes/auth.$.tsx +import { createRouteHandlers } from "@civic/auth/remix"; + +// Create route handlers with your configuration +const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: process.env.CLIENT_ID, +}); + +/** + * Catch-all loader for auth routes + */ +export const loader = createAuthLoader(); + +/** + * Export getUser function for use in other parts of the app + */ +export { getUser }; +``` ### 2. Set Up Root Loader @@ -84,177 +52,51 @@ Configure your root route to provide user data to all routes in your application 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"; // Your CSS imports - - import { getUser } from "~/routes/auth.$"; - - export const links: LinksFunction = () => [ - // Your link definitions - ]; - - export const loader: LoaderFunction = async ({ request }) => { - const user = await getUser(request); - return { - // You need this for the useUser hook to work - user, - }; - }; - - function Layout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - {children} - - - - - ); - } - - export default function App() { - return ( - - - - ); - } - ``` - - - - ```tsx app/root.tsx - import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; - import type { LinksFunction, LoaderFunction } from "react-router"; - import "./tailwind.css"; // Your CSS imports - - import { getUser } from "~/routes/auth.$"; - - export const links: LinksFunction = () => [ - // Your link definitions - ]; - - export const loader: LoaderFunction = async ({ request }) => { - const user = await getUser(request); - return { - // You need this for the useUser hook to work - user, - }; - }; - - function Layout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - {children} - - - - - ); - } - - export default function App() { - return ( - - - - ); - } - ``` - - - - -### 3. Protecting Routes - -To protect routes that require authentication, check for user data in your route loaders and redirect unauthenticated users. - -Example protected route `app/routes/protected.tsx`: - - - - ```tsx app/routes/protected.tsx - import { data, redirect } from "react-router"; - import type { LoaderFunctionArgs, MetaFunction } from "react-router"; - import { getUser } from "./auth.$"; - - export const meta: MetaFunction = () => { - return [{ title: "Protected Page" }]; - }; - - export const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await getUser(request); - - if (!user) { - // Redirect to home or login page - return redirect("/"); - } - - return data({ user }); - }; - - export default function ProtectedPage() { - return ( -
-

Protected Content

-

This page requires authentication to access.

-
- ); - } - ``` - -
- - ```tsx app/routes/protected.tsx - import { data, redirect } from "react-router"; - import type { LoaderFunctionArgs, MetaFunction } from "react-router"; - import { getUser } from "./auth.$"; - - export const meta: MetaFunction = () => { - return [{ title: "Protected Page" }]; - }; - - export const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await getUser(request); - - if (!user) { - // Redirect to home or login page - return redirect("/"); - } - - return data({ user }); - }; - - export default function ProtectedPage() { - return ( -
-

Protected Content

-

This page requires authentication to access.

-
- ); - } - ``` - -
-
+```tsx app/root.tsx +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; +import type { LinksFunction, LoaderFunction } from "react-router"; +import "./tailwind.css"; // Your CSS imports + +import { getUser } from "~/routes/auth.$"; + +export const links: LinksFunction = () => [ + // Your link definitions +]; + +export const loader: LoaderFunction = async ({ request }) => { + const user = await getUser(request); + return { + // You need this for the useUser hook to work + user, + }; +}; + +function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` ## Usage @@ -266,204 +108,226 @@ The React Router 7 integration provides several components and hooks for fronten 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/remix/components/UserButton"; +```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/remix/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}

+
+ )} +
+ ); +} +``` - export const meta: MetaFunction = () => { - return [{ title: "My App" }]; - }; +#### useUser Hook - export async function loader({ request }: LoaderFunctionArgs) { - const user = await getUser(request); - return { user }; - } +For accessing user information in components: - export default function Index() { - const { user } = useLoaderData(); +```tsx MyComponent.tsx +import { useUser } from "@civic/auth/remix"; - return ( -
-

My App

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

Welcome, {user.name}!

-

Email: {user.email}

-
- )} -
- ); - } - ``` - -
- - ```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-web3/remix/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}

-
- )} -
- ); - } - ``` +export function MyComponent() { + const { user, isLoggedIn } = useUser(); -
-
+ if (!isLoggedIn) { + return
Please sign in
; + } -#### useUser Hook + return
Hello {user.name}!
; +} +``` -For accessing user information in components: +#### Custom Authentication Logic - - - ```tsx MyComponent.tsx - import { useUser } from "@civic/auth/remix"; +For custom sign-in buttons and authentication flows: - export function MyComponent() { - const { user, isLoggedIn } = useUser(); +```tsx CustomSignIn.tsx +import { useCallback } from "react"; +import { useUser } from "@civic/auth/remix"; + +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/remix/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.

+
- if (!isLoggedIn) { - return
Please sign in
; - } +
+ + Back to Home + + + Logout + +
+
+
+ ); +} +``` - return
Hello {user.name}!
; - } - ``` +### How It Works -
- - ```tsx MyComponent.tsx - import { useUser } from "@civic/auth-web3/remix"; +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 - export function MyComponent() { - const { user, isLoggedIn } = useUser(); +### Alternative Protection Patterns - if (!isLoggedIn) { - return
Please sign in
; - } +You can also create a reusable protection utility: - return
Hello {user.name}!
; - } - ``` +```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); -#### Custom Authentication Logic + if (!user) { + throw redirect("/auth/login"); + } -For custom sign-in buttons and authentication flows: + return user; +} +``` - - - ```tsx CustomSignIn.tsx - import { startFrontendAuth, logout } from "@civic/auth/remix"; - - export function CustomSignIn() { - const handleSignIn = async () => { - try { - await startFrontendAuth({ - clientId: "YOUR_CLIENT_ID", - displayMode: "iframe", // or "redirect", "new_tab", "embedded" - }); - // Page will revalidate automatically after successful auth - } catch (error) { - console.error("Sign-in failed:", error); - } - }; - - const handleSignOut = async () => { - await logout(); - }; - - return ( -
- - -
- ); - } - ``` - -
- - ```tsx CustomSignIn.tsx - import { useUser } from "@civic/auth/remix"; - - 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 logout(); - }; - - return ( -
- - -
- ); - } - ``` +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); -
-
+ // 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 }; +}; + +// Example helper function - implement according to your business logic +function isUserAdmin(email: string): boolean { + // This is pseudo code - implement your own logic here + // e.g., check against a list of admin emails, database lookup, etc. + const adminEmails = ["admin@yourcompany.com"]; + return adminEmails.includes(email); +} +``` ## Advanced Configuration @@ -471,108 +335,24 @@ Civic Auth is a "low-code" solution, so most configuration takes place via the [ 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/remix"; - - const { createAuthLoader, getUser } = createRouteHandlers({ - clientId: "YOUR_CLIENT_ID", - oauthServer: "https://auth.civic.com/oauth", - callbackUrl: "https://myapp.com/auth/callback", - loginUrl: "/auth/login", - logoutUrl: "/auth/logout", - refreshUrl: "/auth/refresh", - logoutCallbackUrl: "/goodbye", - scope: "openid profile email", - include: ["/admin/*", "/dashboard/*"], // Routes requiring auth - exclude: ["/public/*", "/api/health"], // Public routes - - // Cookie configuration - cookies: { - tokens: { - secure: true, - httpOnly: true, - sameSite: "none", // For iframe compatibility - maxAge: 3600, // 1 hour - }, - user: { - secure: true, - httpOnly: false, - sameSite: "lax", - maxAge: 3600, - }, - }, - - // Frontend integration options - frontend: { - enabled: true, - defaultDisplayMode: "iframe", - authProcessTimeout: 60000, - corsOrigins: ["http://localhost:3000", "https://myapp.com"], - iframeCompatibleCookies: true, - }, - - // Logging configuration - logging: { - enableFor: "@civic/auth:*", - }, - }); - ``` - - - - ```ts app/routes/auth.$.tsx - import { createRouteHandlers } from "@civic/auth-web3/remix"; - - const { createAuthLoader, getUser } = createRouteHandlers({ - clientId: "YOUR_CLIENT_ID", - oauthServer: "https://auth.civic.com/oauth", - callbackUrl: "https://myapp.com/auth/callback", - loginUrl: "/auth/login", - logoutUrl: "/auth/logout", - refreshUrl: "/auth/refresh", - logoutCallbackUrl: "/goodbye", - scope: "openid profile email", - include: ["/admin/*", "/dashboard/*"], // Routes requiring auth - exclude: ["/public/*", "/api/health"], // Public routes - - // Frontend integration options - frontend: { - enabled: true, - defaultDisplayMode: "iframe", - authProcessTimeout: 60000, - corsOrigins: ["http://localhost:3000", "https://myapp.com"], - iframeCompatibleCookies: true, - }, - - // Logging configuration - logging: { - enableFor: "@civic/auth:*", - }, - }); - ``` - - - +```ts app/routes/auth.$.tsx +import { createRouteHandlers } from "@civic/auth/remix"; -### Configuration Options +const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: "YOUR_CLIENT_ID", + loginSuccessUrl: "/myCustomSuccessEndpoint", + callbackUrl: "/api/myroute/callback", + logoutUrl: "/goodbye", + baseUrl: "https://myapp.com", +}); +``` -| 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/) | -| `oauthServer` | No | `https://auth.civic.com/oauth` | `https://auth.civic.com/oauth` | OAuth server URL | -| `callbackUrl` | Yes | - | `https://myapp.com/auth/callback` | OAuth callback URL where users return after authentication | -| `loginUrl` | No | `/auth/login` | `/auth/signin` | Login endpoint path | -| `logoutUrl` | No | `/auth/logout` | `/auth/signout` | Logout endpoint path | -| `refreshUrl` | No | `/auth/refresh` | `/auth/token/refresh` | Token refresh endpoint path | -| `logoutCallbackUrl` | No | `/` | `/goodbye` | The path users are sent to after successful logout | -| `scope` | No | `openid profile email` | `openid profile email offline_access` | OAuth scope | -| `include` | No | `["/"]` | `["/admin/*", "/dashboard/*"]` | An array of path [globs](https://man7.org/linux/man-pages/man7/glob.7.html) that require authentication | -| `exclude` | No | Auth routes | `["/public/*", "/api/health"]` | An array of path [globs](https://man7.org/linux/man-pages/man7/glob.7.html) excluded from authentication requirements | -| `cookies` | No | Secure defaults | See example above | Cookie configuration for tokens and user data | -| `frontend.enabled` | No | `true` | `false` | Enable frontend integration mode for iframe/embedded authentication | -| `frontend.defaultDisplayMode` | No | `iframe` | `redirect` | Default authentication display mode: `iframe`, `redirect`, `new_tab`, or `embedded` | -| `frontend.corsOrigins` | No | `["http://localhost:3000"]` | `["https://myapp.com"]` | CORS origins allowed for frontend integration | -| `logging.enableFor` | No | - | `@civic/auth:*` | Enable logging for debug namespaces | +### 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. | diff --git a/integration/react.mdx b/integration/react.mdx index 7dc7a261..dfae7f01 100644 --- a/integration/react.mdx +++ b/integration/react.mdx @@ -1,466 +1,334 @@ --- -title: "React" -icon: react -description: "Integrate Civic Auth into your React application with ease, just wrap your app with the Civic Auth provider and add your Client ID (provided after you [sign up](https://auth.civic.com))." +title: "React Router 7" +icon: "/images/rr7/rr_logo_light.svg" public: true --- - ## Quick Start -A working example is available in our [github examples repo](https://github.com/civicteam/civic-auth-examples/tree/main/packages/civic-auth/reactjs). +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)): - - This guide assumes you are using Typescript. Please adjust the snippets as needed to remove the types if you are using plain JS. - + + **Important**: Make sure your application is using React Router version ^7.0.0 or higher. This guide is specifically + designed for React Router 7 (formerly Remix). + - If you plan to use Web3 features, select "Auth + Web3" from the tabs below. + This guide assumes you are using TypeScript. Please adjust the snippets as needed to remove the types if you are using + plain JS. - - -```ts App.ts -import { CivicAuthProvider, UserButton } from "@civic/auth/react"; +### 1. Create Auth Route Handlers -function App({ children }) { - return ( - - - {children} - - ) -} -``` - +Create a catch-all route to handle all authentication-related requests. This route will manage login, logout, callback, and user data endpoints. - -```ts App.ts -import { CivicAuthProvider, UserButton } from "@civic/auth-web3/react"; +Create `app/routes/auth.$.tsx`: -function App({ children }) { - return ( - - - {children} - - ) -} +```ts app/routes/auth.$.tsx +import { createRouteHandlers } from "@civic/auth/remix"; + +// Create route handlers with your configuration +const { createAuthLoader, getUser } = createRouteHandlers({ + clientId: process.env.CLIENT_ID, +}); + +/** + * Catch-all loader for auth routes + */ +export const loader = createAuthLoader(); + +/** + * Export getUser function for use in other parts of the app + */ +export { getUser }; ``` - - -## Usage +### 2. Set Up Root Loader -### The User Button +Configure your root route to provide user data to all routes in your application. This enables server-side rendering with authentication state. -The Civic Auth SDK comes with a multi-purpose styled component called the `UserButton` +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"; // Your CSS imports -```ts TitleBar.ts -import { UserButton, CivicAuthProvider } from "@civic/auth/react"; +import { getUser } from "~/routes/auth.$"; -export function TitleBar() { - return ( -
-

My App

- -
- ); +export const links: LinksFunction = () => [ + // Your link definitions +]; + +export const loader: LoaderFunction = async ({ request }) => { + const user = await getUser(request); + return { + // You need this for the useUser hook to work + user, + }; }; -``` -
- -```ts TitleBar.ts -import { UserButton, CivicAuthProvider } from "@civic/auth-web3/react"; -export function TitleBar() { +function Layout({ children }: { children: React.ReactNode }) { return ( -
-

My App

- -
+ + + + + + + + + {children} + + + + ); -}; -``` -
-
-This component is context-dependent. If the user is logged in, it will show their profile picture and name. If the user is not logged in, it will show a Log In button. The button will show a loading spinner while the user is in the process of signing in or signing out. - - - - - -#### Customizing the User Button - -You can customize the styling of the user button by adding either a `className` or `style` property to the UserButton component when declaring it. These styling properties affect both the sign-in button and the user information display when logged in. For further customization, you can also style the buttons that appear in the dropdown menu (which displays when clicking on the user information button) by using the `dropdownButtonClassName` or `dropdownButtonStyle` properties. This gives you granular control over both the main user button and its associated dropdown menu items. Using a className: - - - - ```css style.css - .my-button-container .login-button { - color: red; - background-color: blue; - border: 3px solid #6b7280; - } - - .my-button-container .internal-button { - background-color: red; - color: blue; - border: 3px solid #6b7280; - } - ``` - ```ts CustomUserButtonClassName.ts - import { UserButton, CivicAuthProvider } from "@civic/auth/react"; - - export function TitleBar() { - return ( -
- -
- ); - }; - ``` -
- - - ```css style.css - .my-button-container .login-button { - color: red; - background-color: blue; - border: 3px solid #6b7280; - } - - .my-button-container .internal-button { - background-color: red; - color: blue; - border: 3px solid #6b7280; - } - ``` - ```bash CustomUserButtonClassName.ts - import { UserButton, CivicAuthProvider } from "@civic/auth-web3/react"; - - export function TitleBar() { - return ( -
- -
- ); - }; - ``` -
-
- -Note the use of a *specific* class name declaration in the .css file. This is necessary to ensure that the styles in the imported css className take precedence over internal styles without the use of the discouraged `!important` directive i.e. using just the classname in App.css *would not work*: - -```css -/* this wouldn't override the Civic UserButton styles */ -.login-button { - color: red; - background-color: blue; - border: 3px solid #6b7280; } -``` -Using styles: - - - - ```ts CustomUserButtonStyles.ts - import { UserButton, CivicAuthProvider } from "@civic/auth/react"; - - export function TitleBar() { - return ( -
- -
- ); - }; - ``` -
- - ```ts CustomUserButtonStyles.ts - import { UserButton, CivicAuthProvider } from "@civic/auth-web3/react"; - - export function TitleBar() { - return ( -
- -
- ); - }; - ``` -
-
- -You can also provide values in both `style` and `className` props, where the value in `style` will always take precedence over the same CSS-defined style. - -#### Creating your own Login and Logout buttons - -You can use the `signIn()` and `signOut()` methods from the `useUser()` hook to create your own buttons for user log in and log out - - - - ```ts RollYourOwnLogin.ts - import { CivicAuthProvider } from "@civic/auth/react"; - - export function TitleBar() { - const { signIn, signOut } = useUser(); - return ( -
-

My App

- {!user && } - {user && } -
- ); - }; - ``` -
- -```ts RollYourOwnLogin.ts -import { CivicAuthProvider } from "@civic/auth-web3/react"; - -export function TitleBar() { - const { signIn, signOut } = useUser(); +export default function App() { return ( -
-

My App

- {!user && } - {user && } -
+ + + ); -}; +} ``` -
-
+ +## Usage ### Getting User Information on the Frontend -Use the Civic Auth SDK to retrieve user information on the frontend. +The React Router 7 integration provides several components and hooks for frontend authentication: - - -```ts MyComponent.ts -import { useUser } from "@civic/auth/react"; +#### UserButton Component -export function MyComponent() { - const { user } = useUser(); +The `UserButton` component provides a complete sign-in/sign-out experience: - if (!user) return
User not logged in
+```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/remix/components/UserButton"; - return
Hello { user.name }!
+export const meta: MetaFunction = () => { + return [{ title: "My App" }]; +}; + +export async function loader({ request }: LoaderFunctionArgs) { + const user = await getUser(request); + return { user }; } -``` -
- -```ts MyComponent.ts -import { useUser } from "@civic/auth-web3/react"; -export function MyComponent() { - const { user } = useUser(); +export default function Index() { + const { user } = useLoaderData(); - if (!user) return
User not logged in
+ return ( +
+

My App

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

Welcome, {user.name}!

+

Email: {user.email}

+
+ )} +
+ ); } ``` -
-
- - We use `name` as an example here, but you can call any user object property from the user fields schema, as shown [below](/integration/react#base-user-fields). - +#### useUser Hook -## Advanced Configuration - -Civic Auth is a "low-code" solution, so all 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. The only required parameter you need to provide is the client ID. +For accessing user information in components: -The integration provides additional run-time settings and hooks that you can use to customize the library's integration with your own app. If you need any of these, you can add them to the CivicAuthProvider as follows: - -```ts - -``` -See below for the list of all configuration options +```tsx MyComponent.tsx +import { useUser } from "@civic/auth/remix"; -| 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) | -| nonce | No | - | 1234 | A single-use ID used during login, binding a login token with a given client. Needed in advanced authentication processes only | -| onSignIn | No | - | `(error?: Error) => { if (error) { // handle error } else {// handle successful login}}` | A hook that executes after a sign-in attempt, whether successful or not. | -| onSignOut | No | - | ```() => { // handle signout }``` | A hook that executes after a user logs out. | -| redirectUrl | No | currentURL | /authenticating | An override for the page that OAuth will redirect to to perform token-exchange. By default Civic will redirect to the current URL and Authentication will be finished by the Civic provider automatically. Only use if you'd like to have some custom display or logic during OAuth token-exchange. The redirect page must have the CivicAuthProvider running in order to finish authentication. | -| iframeMode | No | modal | iframeMode=\{"embedded"} | Set to `embedded` if you want to embed the login iframe in your app rather than opening the iframe in a modal. See [Embedded Login Iframe section](/integration/react#embedded-login-iframe) below. | -| displayMode | No | iFrame | "iframe" \| "redirect" \| "new\_tab" | **"iframe"**: Authentication happens in an embedded window within your current page.
**"redirect"**: Full page navigation to the auth server and back to your site after completion.
**"new\_tab"**: Opens auth flow in a new browser tab, returning to original tab after completion. | - -### Display Mode +export function MyComponent() { + const { user, isLoggedIn } = useUser(); -The display mode indicates where the Civic login UI will be displayed. The following display modes are supported: + if (!isLoggedIn) { + return
Please sign in
; + } -* `iframe` (default): the UI loads in an iframe that shows in an overlay on top of the existing page content + return
Hello {user.name}!
; +} +``` -* `redirect`: the UI redirects the current URL to a Civic login screen, then redirects back to your site when login is complete +#### Custom Authentication Logic -* `new_tab`: the UI opens in a new tab or popup window (depending on browser preferences), and after login is complete, the tab or popup closes to return the user to your site +For custom sign-in buttons and authentication flows: -## API +```tsx CustomSignIn.tsx +import { useCallback } from "react"; +import { useUser } from "@civic/auth/remix"; -### User Context +export function CustomSignIn() { + const { signIn, signOut } = useUser(); -The full user context object (provided by `useUser`) looks like this: + 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(); + }; -```js -{ - user: User | null; - // these are the OAuth tokens created during authentication - idToken?: string; - accessToken?: string; - refreshToken?: string; - forwardedTokens?: ForwardedTokens; - // functions and flags for UI and signIn/signOut - isLoading: boolean; - authStatus: AuthStatus; - error: Error | null; - signIn: (displayMode?: DisplayMode) => Promise; - signOut: () => Promise; + return ( +
+ + +
+ ); } ``` -#### AuthStatus +## Protected Routes -The `authStatus` field exposed in the UserContext can be used to update your UI depending on the user's authentication status, i.e. update the UI to show a loader while the user is in the process of authenticating or signing out. +React Router 7 supports route-level authentication using loaders. Here's how to create protected routes that require user authentication: -```json -export enum AuthStatus { - AUTHENTICATED = "authenticated", - UNAUTHENTICATED = "unauthenticated", - AUTHENTICATING = "authenticating", - ERROR = "error", - SIGNING_OUT = "signing_out", -} -``` +### Creating a Protected Route -### User +Create a protected route loader that checks authentication and redirects unauthenticated users: -The `User` object looks like this: +```tsx app/routes/protected._index.tsx +import { data, redirect, Outlet } from "react-router"; +import type { LoaderFunctionArgs, MetaFunction } from "react-router"; +import { getUser } from "./auth.$"; -```json -type BaseUser = { - id: string; - email?: string; - name?: string; - picture?: string; - given_name?: string; - family_name?: string; - updated_at?: Date; +export const meta: MetaFunction = () => { + return [{ title: "Protected Page - Civic Auth Demo" }]; }; -type User = BaseUser & T; -``` +export const loader = async ({ request }: LoaderFunctionArgs) => { + const user = await getUser(request); -Where you can pass extra user attributes to the object that you know will be present in user claims, e.g. + if (!user) { + // Redirect to home page if user is not authenticated + return redirect("/"); + } + + return data({ user }); +}; -```json -const UserWithNickName = User<{ nickname: string }>; +export default function ProtectedPage() { + return ( +
+ +
+ ); +} ``` -Field descriptions: +### Protected Route Layout -#### Base User Fields +Create the protected route content that displays to authenticated users: -| Field | | -| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | -| id | The user's unique ID with respect to your app. You can use this to look up the user in the [dashboard](https://auth.civic.com/dashboard). | -| email | The user's email address | -| name | The user's full name | -| given\_name | The user's given name | -| family\_name | The user's family name | -| updated\_at | The time at which the user's profile was most recently updated. | +```tsx app/routes/protected.tsx +import { Link } from "react-router"; +import type { MetaFunction } from "react-router"; +import { useUser } from "@civic/auth/remix/useUser"; +export const meta: MetaFunction = () => { + return [{ title: "Protected Page - Civic Auth Demo" }]; +}; -#### Token Fields +export default function ProtectedLayout() { + const { user } = useUser(); - - Typically developers will not need to interact with the token fields, which are used only for advanced use cases. - + return ( +
+
+

Protected Page

-| Field | | -| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| idToken | The OIDC id token, used to request identity information about the user | -| accessToken | The OAuth 2.0 access token, allowing a client to make API calls to Civic Auth on behalf of the user. | -| refreshToken | The OAuth 2.0 refresh token, allowing a login session to be extended automatically without requiring user interaction. The Civic Auth SDK handles refresh automatically, so you do not need to do this. | -| forwardedTokens | If the user authenticated using SSO (single-sign-on login) with a federated OAuth provider such as Google, this contains the OIDC and OAuth 2.0 tokens from that provider. | +
+

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

+

This content is only visible to authenticated users.

+
-#### Forwarded Tokens +
+ + Back to Home + + + Logout + +
+
+
+ ); +} +``` -Use forwardedTokens if you need to make requests to the source provider, such as find out provider-specific information. +### How It Works -An example would be, if a user logged in via Google, using the Google forwarded token to query the Google Groups that the user is a member of. +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 -For example: +### Alternative Protection Patterns -```ts -const googleAccessToken = user.forwardedTokens?.google?.accessToken; -``` +You can also create more complex protected routes with additional checks: -#### Embedded Login Iframe +```tsx app/routes/admin.tsx +import { redirect } from "react-router"; +import type { LoaderFunctionArgs } from "react-router"; +import { getUser } from "./auth.$"; -If you want to have the Login screen open directly on a page without the user having to click on button, you can import the `CivicAuthIframeContainer` component along with the AuthProvider option iframeMode`={"embedded"}` +export const loader = async ({ request }: LoaderFunctionArgs) => { + const user = await getUser(request); -You just need to ensure that the `CivicAuthIframeContainer` is a child under a `CivicAuthProvider` + if (!user) { + return redirect("/"); + } - - -```ts App.ts -import { CivicAuthIframeContainer } from "@civic/auth/react"; + // Additional admin-specific checks can go here + if (user.role !== "admin") { + throw redirect("/"); + } -const Login = () => { - return ( -
- -
- ); + return { user }; }; - -const App = () => { - return ( - - - - ); -} ``` -
- -```ts App.ts -import { CivicAuthIframeContainer } from "@civic/auth-web3/react"; -const Login = () => { - return ( -
- -
- ); -}; +## Advanced Configuration -const App = () => { - return ( - - - - ); -} +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/remix"; + +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. | From ffe8113337337ec7d6b24b85176edcec3b37336f Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Wed, 9 Jul 2025 12:04:09 +0200 Subject: [PATCH 3/8] fix(rr7): Reverting react docs which was wrongly modified --- integration/react.mdx | 618 +++++++++++++++++++++++++----------------- 1 file changed, 375 insertions(+), 243 deletions(-) diff --git a/integration/react.mdx b/integration/react.mdx index dfae7f01..7dc7a261 100644 --- a/integration/react.mdx +++ b/integration/react.mdx @@ -1,334 +1,466 @@ --- -title: "React Router 7" -icon: "/images/rr7/rr_logo_light.svg" +title: "React" +icon: react +description: "Integrate Civic Auth into your React application with ease, just wrap your app with the Civic Auth provider and add your Client ID (provided after you [sign up](https://auth.civic.com))." public: true --- -## 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)): +## Quick Start - - **Important**: Make sure your application is using React Router version ^7.0.0 or higher. This guide is specifically - designed for React Router 7 (formerly Remix). - +A working example is available in our [github examples repo](https://github.com/civicteam/civic-auth-examples/tree/main/packages/civic-auth/reactjs). - This guide assumes you are using TypeScript. Please adjust the snippets as needed to remove the types if you are using - plain JS. + 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 { createRouteHandlers } from "@civic/auth/remix"; - -// Create route handlers with your configuration -const { createAuthLoader, getUser } = createRouteHandlers({ - clientId: process.env.CLIENT_ID, -}); - -/** - * Catch-all loader for auth routes - */ -export const loader = createAuthLoader(); - -/** - * Export getUser function for use in other parts of the app - */ -export { getUser }; -``` - -### 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"; // Your CSS imports - -import { getUser } from "~/routes/auth.$"; - -export const links: LinksFunction = () => [ - // Your link definitions -]; + + If you plan to use Web3 features, select "Auth + Web3" from the tabs below. + -export const loader: LoaderFunction = async ({ request }) => { - const user = await getUser(request); - return { - // You need this for the useUser hook to work - user, - }; -}; + + +```ts App.ts +import { CivicAuthProvider, UserButton } from "@civic/auth/react"; -function Layout({ children }: { children: React.ReactNode }) { +function App({ children }) { return ( - - - - - - - - - {children} - - - - - ); + + + {children} + + ) } +``` + + + +```ts App.ts +import { CivicAuthProvider, UserButton } from "@civic/auth-web3/react"; -export default function App() { +function App({ children }) { return ( - - - - ); + + + {children} + + ) } ``` + + ## Usage -### Getting User Information on the Frontend - -The React Router 7 integration provides several components and hooks for frontend authentication: +### The User Button -#### UserButton Component +The Civic Auth SDK comes with a multi-purpose styled component called the `UserButton` -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/remix/components/UserButton"; +```ts TitleBar.ts +import { UserButton, CivicAuthProvider } from "@civic/auth/react"; -export const meta: MetaFunction = () => { - return [{ title: "My App" }]; +export function TitleBar() { + return ( +
+

My App

+ +
+ ); }; +``` +
+ +```ts TitleBar.ts +import { UserButton, CivicAuthProvider } from "@civic/auth-web3/react"; -export async function loader({ request }: LoaderFunctionArgs) { - const user = await getUser(request); - return { user }; -} - -export default function Index() { - const { user } = useLoaderData(); - +export function TitleBar() { return (

My App

- console.log("User signed in", user)} - onSignOut={() => console.log("User signed out")} - /> + +
+ ); +}; +``` +
+
+This component is context-dependent. If the user is logged in, it will show their profile picture and name. If the user is not logged in, it will show a Log In button. The button will show a loading spinner while the user is in the process of signing in or signing out. + + + + + +#### Customizing the User Button + +You can customize the styling of the user button by adding either a `className` or `style` property to the UserButton component when declaring it. These styling properties affect both the sign-in button and the user information display when logged in. For further customization, you can also style the buttons that appear in the dropdown menu (which displays when clicking on the user information button) by using the `dropdownButtonClassName` or `dropdownButtonStyle` properties. This gives you granular control over both the main user button and its associated dropdown menu items. Using a className: + + + + ```css style.css + .my-button-container .login-button { + color: red; + background-color: blue; + border: 3px solid #6b7280; + } + + .my-button-container .internal-button { + background-color: red; + color: blue; + border: 3px solid #6b7280; + } + ``` + ```ts CustomUserButtonClassName.ts + import { UserButton, CivicAuthProvider } from "@civic/auth/react"; + + export function TitleBar() { + return ( +
+ +
+ ); + }; + ``` +
+ + + ```css style.css + .my-button-container .login-button { + color: red; + background-color: blue; + border: 3px solid #6b7280; + } + + .my-button-container .internal-button { + background-color: red; + color: blue; + border: 3px solid #6b7280; + } + ``` + ```bash CustomUserButtonClassName.ts + import { UserButton, CivicAuthProvider } from "@civic/auth-web3/react"; + + export function TitleBar() { + return ( +
+ +
+ ); + }; + ``` +
+
+ +Note the use of a *specific* class name declaration in the .css file. This is necessary to ensure that the styles in the imported css className take precedence over internal styles without the use of the discouraged `!important` directive i.e. using just the classname in App.css *would not work*: + +```css +/* this wouldn't override the Civic UserButton styles */ +.login-button { + color: red; + background-color: blue; + border: 3px solid #6b7280; +} +``` - {user && ( +Using styles: + + + + ```ts CustomUserButtonStyles.ts + import { UserButton, CivicAuthProvider } from "@civic/auth/react"; + + export function TitleBar() { + return (
-

Welcome, {user.name}!

-

Email: {user.email}

+
- )} + ); + }; + ``` +
+ + ```ts CustomUserButtonStyles.ts + import { UserButton, CivicAuthProvider } from "@civic/auth-web3/react"; + + export function TitleBar() { + return ( +
+ +
+ ); + }; + ``` +
+
+ +You can also provide values in both `style` and `className` props, where the value in `style` will always take precedence over the same CSS-defined style. + +#### Creating your own Login and Logout buttons + +You can use the `signIn()` and `signOut()` methods from the `useUser()` hook to create your own buttons for user log in and log out + + + + ```ts RollYourOwnLogin.ts + import { CivicAuthProvider } from "@civic/auth/react"; + + export function TitleBar() { + const { signIn, signOut } = useUser(); + return ( +
+

My App

+ {!user && } + {user && } +
+ ); + }; + ``` +
+ +```ts RollYourOwnLogin.ts +import { CivicAuthProvider } from "@civic/auth-web3/react"; + +export function TitleBar() { + const { signIn, signOut } = useUser(); + return ( +
+

My App

+ {!user && } + {user && }
); -} +}; ``` +
+
-#### useUser Hook +### Getting User Information on the Frontend -For accessing user information in components: +Use the Civic Auth SDK to retrieve user information on the frontend. -```tsx MyComponent.tsx -import { useUser } from "@civic/auth/remix"; + + +```ts MyComponent.ts +import { useUser } from "@civic/auth/react"; export function MyComponent() { - const { user, isLoggedIn } = useUser(); + const { user } = useUser(); - if (!isLoggedIn) { - return
Please sign in
; - } + if (!user) return
User not logged in
- return
Hello {user.name}!
; + return
Hello { user.name }!
} ``` +
+ +```ts MyComponent.ts +import { useUser } from "@civic/auth-web3/react"; -#### Custom Authentication Logic +export function MyComponent() { + const { user } = useUser(); -For custom sign-in buttons and authentication flows: + if (!user) return
User not logged in
-```tsx CustomSignIn.tsx -import { useCallback } from "react"; -import { useUser } from "@civic/auth/remix"; + return
Hello { user.name }!
+} +``` +
+
-export function CustomSignIn() { - const { signIn, signOut } = useUser(); + + We use `name` as an example here, but you can call any user object property from the user fields schema, as shown [below](/integration/react#base-user-fields). + - 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(); - }; +## Advanced Configuration - return ( -
- - -
- ); -} +Civic Auth is a "low-code" solution, so all 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. The only required parameter you need to provide is the client ID. + +The integration provides additional run-time settings and hooks that you can use to customize the library's integration with your own app. If you need any of these, you can add them to the CivicAuthProvider as follows: + +```ts + ``` +See below for the list of all configuration options -## Protected Routes +| 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) | +| nonce | No | - | 1234 | A single-use ID used during login, binding a login token with a given client. Needed in advanced authentication processes only | +| onSignIn | No | - | `(error?: Error) => { if (error) { // handle error } else {// handle successful login}}` | A hook that executes after a sign-in attempt, whether successful or not. | +| onSignOut | No | - | ```() => { // handle signout }``` | A hook that executes after a user logs out. | +| redirectUrl | No | currentURL | /authenticating | An override for the page that OAuth will redirect to to perform token-exchange. By default Civic will redirect to the current URL and Authentication will be finished by the Civic provider automatically. Only use if you'd like to have some custom display or logic during OAuth token-exchange. The redirect page must have the CivicAuthProvider running in order to finish authentication. | +| iframeMode | No | modal | iframeMode=\{"embedded"} | Set to `embedded` if you want to embed the login iframe in your app rather than opening the iframe in a modal. See [Embedded Login Iframe section](/integration/react#embedded-login-iframe) below. | +| displayMode | No | iFrame | "iframe" \| "redirect" \| "new\_tab" | **"iframe"**: Authentication happens in an embedded window within your current page.
**"redirect"**: Full page navigation to the auth server and back to your site after completion.
**"new\_tab"**: Opens auth flow in a new browser tab, returning to original tab after completion. | -React Router 7 supports route-level authentication using loaders. Here's how to create protected routes that require user authentication: +### Display Mode -### Creating a Protected Route +The display mode indicates where the Civic login UI will be displayed. The following display modes are supported: -Create a protected route loader that checks authentication and redirects unauthenticated users: +* `iframe` (default): the UI loads in an iframe that shows in an overlay on top of the existing page content -```tsx app/routes/protected._index.tsx -import { data, redirect, Outlet } from "react-router"; -import type { LoaderFunctionArgs, MetaFunction } from "react-router"; -import { getUser } from "./auth.$"; +* `redirect`: the UI redirects the current URL to a Civic login screen, then redirects back to your site when login is complete -export const meta: MetaFunction = () => { - return [{ title: "Protected Page - Civic Auth Demo" }]; -}; +* `new_tab`: the UI opens in a new tab or popup window (depending on browser preferences), and after login is complete, the tab or popup closes to return the user to your site -export const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await getUser(request); +## API - if (!user) { - // Redirect to home page if user is not authenticated - return redirect("/"); - } +### User Context - return data({ user }); -}; +The full user context object (provided by `useUser`) looks like this: -export default function ProtectedPage() { - return ( -
- -
- ); +```js +{ + user: User | null; + // these are the OAuth tokens created during authentication + idToken?: string; + accessToken?: string; + refreshToken?: string; + forwardedTokens?: ForwardedTokens; + // functions and flags for UI and signIn/signOut + isLoading: boolean; + authStatus: AuthStatus; + error: Error | null; + signIn: (displayMode?: DisplayMode) => Promise; + signOut: () => Promise; } ``` -### Protected Route Layout +#### AuthStatus -Create the protected route content that displays to authenticated users: +The `authStatus` field exposed in the UserContext can be used to update your UI depending on the user's authentication status, i.e. update the UI to show a loader while the user is in the process of authenticating or signing out. -```tsx app/routes/protected.tsx -import { Link } from "react-router"; -import type { MetaFunction } from "react-router"; -import { useUser } from "@civic/auth/remix/useUser"; +```json +export enum AuthStatus { + AUTHENTICATED = "authenticated", + UNAUTHENTICATED = "unauthenticated", + AUTHENTICATING = "authenticating", + ERROR = "error", + SIGNING_OUT = "signing_out", +} +``` -export const meta: MetaFunction = () => { - return [{ title: "Protected Page - Civic Auth Demo" }]; -}; +### User -export default function ProtectedLayout() { - const { user } = useUser(); +The `User` object looks like this: - return ( -
-
-

Protected Page

+```json +type BaseUser = { + id: string; + email?: string; + name?: string; + picture?: string; + given_name?: string; + family_name?: string; + updated_at?: Date; +}; -
-

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

-

This content is only visible to authenticated users.

-
+type User = BaseUser & T; +``` -
- - Back to Home - - - Logout - -
-
-
- ); -} +Where you can pass extra user attributes to the object that you know will be present in user claims, e.g. + +```json +const UserWithNickName = User<{ nickname: string }>; ``` -### How It Works +Field descriptions: -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 +#### Base User Fields -### Alternative Protection Patterns +| Field | | +| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| id | The user's unique ID with respect to your app. You can use this to look up the user in the [dashboard](https://auth.civic.com/dashboard). | +| email | The user's email address | +| name | The user's full name | +| given\_name | The user's given name | +| family\_name | The user's family name | +| updated\_at | The time at which the user's profile was most recently updated. | -You can also create more complex protected routes with additional checks: -```tsx app/routes/admin.tsx -import { redirect } from "react-router"; -import type { LoaderFunctionArgs } from "react-router"; -import { getUser } from "./auth.$"; +#### Token Fields -export const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await getUser(request); + + Typically developers will not need to interact with the token fields, which are used only for advanced use cases. + - if (!user) { - return redirect("/"); - } +| Field | | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| idToken | The OIDC id token, used to request identity information about the user | +| accessToken | The OAuth 2.0 access token, allowing a client to make API calls to Civic Auth on behalf of the user. | +| refreshToken | The OAuth 2.0 refresh token, allowing a login session to be extended automatically without requiring user interaction. The Civic Auth SDK handles refresh automatically, so you do not need to do this. | +| forwardedTokens | If the user authenticated using SSO (single-sign-on login) with a federated OAuth provider such as Google, this contains the OIDC and OAuth 2.0 tokens from that provider. | - // Additional admin-specific checks can go here - if (user.role !== "admin") { - throw redirect("/"); - } +#### Forwarded Tokens - return { user }; -}; +Use forwardedTokens if you need to make requests to the source provider, such as find out provider-specific information. + +An example would be, if a user logged in via Google, using the Google forwarded token to query the Google Groups that the user is a member of. + +For example: + +```ts +const googleAccessToken = user.forwardedTokens?.google?.accessToken; ``` -## Advanced Configuration +#### Embedded Login Iframe -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. +If you want to have the Login screen open directly on a page without the user having to click on button, you can import the `CivicAuthIframeContainer` component along with the AuthProvider option iframeMode`={"embedded"}` -You can customize the library according to your React Router 7 app's needs. Configure options when calling `createRouteHandlers`: +You just need to ensure that the `CivicAuthIframeContainer` is a child under a `CivicAuthProvider` -```ts app/routes/auth.$.tsx -import { createRouteHandlers } from "@civic/auth/remix"; + + +```ts App.ts +import { CivicAuthIframeContainer } from "@civic/auth/react"; -const { createAuthLoader, getUser } = createRouteHandlers({ - clientId: "YOUR_CLIENT_ID", - loginSuccessUrl: "/myCustomSuccessEndpoint", - callbackUrl: "/api/myroute/callback", - logoutUrl: "/goodbye", - baseUrl: "https://myapp.com", -}); +const Login = () => { + return ( +
+ +
+ ); +}; + +const App = () => { + return ( + + + + ); +} ``` +
+ +```ts App.ts +import { CivicAuthIframeContainer } from "@civic/auth-web3/react"; -### Configuration Options +const Login = () => { + return ( +
+ +
+ ); +}; + +const App = () => { + return ( + + + + ); +} +``` +
+
-| 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. | From 0b2e08fbc1280fa44e2c66f8e922e3522779b50e Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Wed, 9 Jul 2025 13:22:54 +0200 Subject: [PATCH 4/8] docs(rr7): Simplifying the sugar coated requireAuth --- integration/react-router.mdx | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/integration/react-router.mdx b/integration/react-router.mdx index a1c0e056..ed9d3850 100644 --- a/integration/react-router.mdx +++ b/integration/react-router.mdx @@ -298,6 +298,12 @@ export async function requireAuth(request: Request) { 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; } ``` @@ -311,22 +317,9 @@ import { requireAuth } from "~/utils/requireAuth"; export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await requireAuth(request); - // 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 }; }; -// Example helper function - implement according to your business logic -function isUserAdmin(email: string): boolean { - // This is pseudo code - implement your own logic here - // e.g., check against a list of admin emails, database lookup, etc. - const adminEmails = ["admin@yourcompany.com"]; - return adminEmails.includes(email); -} ``` ## Advanced Configuration From 2cc82ce25c192526b24455a76b352f64c5c150d7 Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Mon, 14 Jul 2025 09:15:10 +0200 Subject: [PATCH 5/8] docs(rr7): Updating the docs with the new API --- integration/react-router.mdx | 67 +++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/integration/react-router.mdx b/integration/react-router.mdx index ed9d3850..ac166ce5 100644 --- a/integration/react-router.mdx +++ b/integration/react-router.mdx @@ -5,16 +5,15 @@ 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. + **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. - +**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 @@ -28,11 +27,15 @@ Create a catch-all route to handle all authentication-related requests. This rou Create `app/routes/auth.$.tsx`: ```ts app/routes/auth.$.tsx -import { createRouteHandlers } from "@civic/auth/remix"; +// 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 your configuration -const { createAuthLoader, getUser } = createRouteHandlers({ +// Create route handlers with app config +const { createAuthLoader, getUser, getAuthData } = createRouteHandlers({ clientId: process.env.CLIENT_ID, + oauthServer: process.env.OAUTH_SERVER, + loginUrl: process.env.LOGIN_URL, }); /** @@ -41,9 +44,9 @@ const { createAuthLoader, getUser } = createRouteHandlers({ export const loader = createAuthLoader(); /** - * Export getUser function for use in other parts of the app + * Export getUser and getAuthData functions for use in other parts of the app */ -export { getUser }; +export { getUser, getAuthData }; ``` ### 2. Set Up Root Loader @@ -55,19 +58,29 @@ 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"; // Your CSS imports +import "./tailwind.css"; -import { getUser } from "~/routes/auth.$"; +import { getAuthData } from "~/routes/auth.$"; export const links: LinksFunction = () => [ - // Your link definitions + { 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 user = await getUser(request); + const authData = await getAuthData(request); + return { - // You need this for the useUser hook to work - user, + // We bootstrap our data through the stack + ...authData, }; }; @@ -89,6 +102,19 @@ function Layout({ children }: { children: React.ReactNode }) { ); } +export function ErrorBoundary() { + return ( + +
+
+

Something went wrong

+

Please try refreshing the page

+
+
+
+ ); +} + export default function App() { return ( @@ -112,7 +138,7 @@ The `UserButton` component provides a complete sign-in/sign-out experience: import { useLoaderData } from "react-router"; import type { LoaderFunctionArgs, MetaFunction } from "react-router"; import { getUser } from "~/routes/auth.$"; -import { UserButton } from "@civic/auth/remix/components/UserButton"; +import { UserButton } from "@civic/auth/react-router-7/components/UserButton"; export const meta: MetaFunction = () => { return [{ title: "My App" }]; @@ -150,7 +176,7 @@ export default function Index() { For accessing user information in components: ```tsx MyComponent.tsx -import { useUser } from "@civic/auth/remix"; +import { useUser } from "@civic/auth/react-router-7"; export function MyComponent() { const { user, isLoggedIn } = useUser(); @@ -169,7 +195,7 @@ For custom sign-in buttons and authentication flows: ```tsx CustomSignIn.tsx import { useCallback } from "react"; -import { useUser } from "@civic/auth/remix"; +import { useUser } from "@civic/auth/react-router-7"; export function CustomSignIn() { const { signIn, signOut } = useUser(); @@ -242,7 +268,7 @@ 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/remix/useUser"; +import { useUser } from "@civic/auth/react-router-7/useUser"; export const meta: MetaFunction = () => { return [{ title: "Protected Page - Civic Auth Demo" }]; @@ -319,7 +345,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { return { user }; }; - ``` ## Advanced Configuration @@ -329,7 +354,7 @@ Civic Auth is a "low-code" solution, so most configuration takes place via the [ 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/remix"; +import { createRouteHandlers } from "@civic/auth/react-router-7"; const { createAuthLoader, getUser } = createRouteHandlers({ clientId: "YOUR_CLIENT_ID", From c19d4c78ed0b171626b60ed0250fc5a47296af8d Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Wed, 16 Jul 2025 16:31:01 +0200 Subject: [PATCH 6/8] docs(rr7): Adding the actions too to the examples. refresh needs to be a post --- integration/react-router.mdx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/integration/react-router.mdx b/integration/react-router.mdx index ac166ce5..1dc3fb1b 100644 --- a/integration/react-router.mdx +++ b/integration/react-router.mdx @@ -32,17 +32,20 @@ Create `app/routes/auth.$.tsx`: import { createRouteHandlers } from "@civic/auth/react-router-7"; // Create route handlers with app config -const { createAuthLoader, getUser, getAuthData } = createRouteHandlers({ - clientId: process.env.CLIENT_ID, - oauthServer: process.env.OAUTH_SERVER, - loginUrl: process.env.LOGIN_URL, +const { createAuthLoader, createAuthAction, getUser, getAuthData } = createRouteHandlers({ + clientId: "demo-client-1" }); /** - * Catch-all loader for auth routes + * 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 */ From 00febe06ed8f5ada483ee549b2f6359d8d0460d9 Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Fri, 1 Aug 2025 10:19:27 +0200 Subject: [PATCH 7/8] POC(rr7): With Middleware refactoring --- integration/react-router.mdx | 143 +++++++++++++++-------------------- 1 file changed, 60 insertions(+), 83 deletions(-) diff --git a/integration/react-router.mdx b/integration/react-router.mdx index 1dc3fb1b..8195c5f0 100644 --- a/integration/react-router.mdx +++ b/integration/react-router.mdx @@ -20,50 +20,24 @@ Integrate Civic Auth into your React Router 7 application using the following st plain JS.
-### 1. Create Auth Route Handlers +### 1. Set Up Authentication Middleware -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. +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, LoaderFunction } from "react-router"; +import type { LinksFunction, Route } from "react-router"; import "./tailwind.css"; -import { getAuthData } from "~/routes/auth.$"; +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" }, @@ -78,14 +52,10 @@ export const links: LinksFunction = () => [ }, ]; -export const loader: LoaderFunction = async ({ request }) => { - const authData = await getAuthData(request); - - return { - // We bootstrap our data through the stack - ...authData, - }; -}; +export function loader({ context }: Route.LoaderArgs) { + const user = context.get(userContext); // Guaranteed to exist + return { user }; +} function Layout({ children }: { children: React.ReactNode }) { return ( @@ -147,14 +117,7 @@ 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

@@ -229,23 +192,24 @@ export function CustomSignIn() { ## Protected Routes -React Router 7 supports route-level authentication using loaders. Here's how to create protected routes that require user authentication: +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 loader that checks authentication and redirects unauthenticated users: +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 { LoaderFunctionArgs, MetaFunction } from "react-router"; -import { getUser } from "./auth.$"; +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 const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await getUser(request); +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 @@ -253,7 +217,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { } return data({ user }); -}; +} export default function ProtectedPage() { return ( @@ -306,29 +270,28 @@ export default function ProtectedLayout() { ### 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 +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 -You can also create a reusable protection utility: +#### Reusable Protection Utilities ```tsx app/utils/requireAuth.ts import { redirect } from "react-router"; -import { getUser } from "~/routes/auth.$"; +import { userContext } from "@civic/auth/react-router-7"; -export async function requireAuth(request: Request) { - const user = await getUser(request); +export function requireAuth(context: any) { + const user = context.get(userContext); 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("/"); } @@ -340,32 +303,46 @@ export async function requireAuth(request: Request) { Then use it in any protected route: ```tsx app/routes/admin.tsx -import type { LoaderFunctionArgs } from "react-router"; +import type { Route } from "react-router"; import { requireAuth } from "~/utils/requireAuth"; -export const loader = async ({ request }: LoaderFunctionArgs) => { - const user = await requireAuth(request); +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`: -```ts app/routes/auth.$.tsx -import { createRouteHandlers } from "@civic/auth/react-router-7"; +### Middleware Configuration -const { createAuthLoader, getUser } = createRouteHandlers({ - clientId: "YOUR_CLIENT_ID", - loginSuccessUrl: "/myCustomSuccessEndpoint", - callbackUrl: "/api/myroute/callback", - logoutUrl: "/goodbye", - baseUrl: "https://myapp.com", -}); +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 @@ -373,7 +350,7 @@ const { createAuthLoader, getUser } = createRouteHandlers({ | 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. | +| `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. | From 456fedf1586b38dd8d494f425274866d193bb47a Mon Sep 17 00:00:00 2001 From: Arnaldo Capo Date: Fri, 1 Aug 2025 10:34:43 +0200 Subject: [PATCH 8/8] POC(rr7): Adding docs about the feature flag the user needs to enable --- integration/react-router.mdx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/integration/react-router.mdx b/integration/react-router.mdx index 8195c5f0..7eff3a50 100644 --- a/integration/react-router.mdx +++ b/integration/react-router.mdx @@ -20,7 +20,23 @@ Integrate Civic Auth into your React Router 7 application using the following st plain JS. -### 1. Set Up Authentication Middleware +### 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.