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:
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 || {};