Skip to main content

React Router (Declarative)

For applications that need declarative routing with protected routes, you can combine Scute's React hooks with React Router. This approach provides a clean separation of concerns where each authentication step is handled as a separate route, and protected routes automatically redirect unauthenticated users.

Head over to the example projects repo to see the React Router implementation in action and check out the type docs for more scuteClient methods.

To get started, install our React SDK and React Router with your favorite package manager:

Terminal
npm install @scute/react-hooks react-router

Add your credentials to your environment variable handler:

VITE_SCUTE_APP_ID="YOUR_SCUTE_PROJECT_ID"
VITE_SCUTE_BASE_URL="YOUR_SCUTE_BASE_URL"

NOTE: If you are not using Vite, use "REACT_APP" as your prefix for your environment variables.

Initialize the Scute client

First initialize the Scute client using the createClient method exposed by @scute/react-hooks package:

// providers.jsx

import { createClient, AuthContextProvider } from "@scute/react-hooks";

const scuteClient = createClient({
appId: import.meta.env.VITE_SCUTE_APP_ID,
baseUrl: import.meta.env.VITE_SCUTE_BASE_URL,
});

export default function Providers({ children }) {

return (
<AuthContextProvider scuteClient={scuteClient}>
{children}
</AuthContextProvider>
);
}

Wrap your React app with providers and router

Set up your app with both the Scute provider and React Router:

// main.jsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.jsx";
import Providers from "./providers.jsx";

createRoot(document.getElementById("root")).render(
<StrictMode>
<Providers>
<BrowserRouter>
<App />
</BrowserRouter>
</Providers>
</StrictMode>
);

Set up your routes

Define your routes declaratively with React Router:

// App.jsx

import { Routes, Route, Navigate } from "react-router-dom";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { Login } from "./components/Login";
import { Profile } from "./components/Profile";
// ... other component imports

function App() {
return (
<Routes>
<Route path="/" element={<MagicVerify />} />
<Route path="/login" element={<LoginForm />} />
<Route path="/profile" element={<Profile />} />
<Route path="/magic-sent" element={<MagicSent />} />
<Route path="/register-device" element={<RegisterDevice />} />
<Route path="/otp-verify" element={<OtpForm />} />
</Routes>
);
}

Create route components

Each authentication step becomes its own component. Here's an example login component:

// components/Login.jsx

import { useScuteClient } from "@scute/react-hooks";
import { useState } from "react";
import { useNavigate } from "react-router-dom";

export function Login() {
const [identifier, setIdentifier] = useState("");
const scuteClient = useScuteClient();
const navigate = useNavigate();

const handleSubmit = async (e) => {
e.preventDefault();
const { data, error } = await scuteClient.signInOrUp(identifier);

if (error) {
console.error("Sign in error:", error);
return;
}

if (!data) {
// Passkey verified, go to profile
navigate("/profile");
} else {
// Navigate to appropriate verification step
if (identifier.includes("@")) {
navigate("/magic-sent", { state: { identifier } });
} else {
navigate("/otp-verify", { state: { identifier } });
}
}
};

return (
<form onSubmit={handleSubmit}>
<h2>Sign In</h2>
<input
type="text"
placeholder="Email or phone"
value={identifier}
onChange={(e) => setIdentifier(e.target.value)}
/>
<button type="submit">Sign In</button>
</form>
);
}

And a protected profile component:

// components/Profile.jsx

import { useAuth, useScuteClient } from "@scute/react-hooks";
import { useNavigate } from "react-router-dom";

export function Profile() {
const { user } = useAuth();
const scuteClient = useScuteClient();
const navigate = useNavigate();

const handleSignOut = async () => {
await scuteClient.signOut();
navigate("/login");
};

return (
<div>
<h2>Welcome, {user?.email}</h2>
<pre>{JSON.stringify(user, null, 2)}</pre>
<button onClick={handleSignOut}>Sign Out</button>
</div>
);
}

Route navigation patterns

Use React Router's navigation hooks to move between authentication steps:

// Navigate programmatically
const navigate = useNavigate();
navigate("/profile");

// Navigate with state
navigate("/otp-verify", { state: { identifier } });

// Access state in destination component
const location = useLocation();
const { identifier } = location.state || {};