Skip to main content

TypeScript / React

@identsphere/react is the official React client. It includes typed hooks for every endpoint, an Axios client with refresh + CSRF interceptors, and route guards.

Install

npm install @identsphere/react @tanstack/react-query

@tanstack/react-query v5 is a peer dep.

Provider

Wrap your app:

// main.tsx
import { AuthProvider } from '@identsphere/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<AuthProvider
config={{
apiBaseUrl: import.meta.env.VITE_AUTH_API_URL!,
// optional overrides:
accessCookieName: 'identsphere_at',
csrfCookieName: 'identsphere_csrf',
}}
>
<App />
</AuthProvider>
</QueryClientProvider>
);

Hooks

Authentication

import { useLogin, useLogout, useRegister } from '@identsphere/react';

function LoginForm() {
const login = useLogin();

return (
<form onSubmit={async (e) => {
e.preventDefault();
const data = new FormData(e.currentTarget);
const res = await login.mutateAsync({
email: data.get('email') as string,
password: data.get('password') as string,
});
if (res.status === 'mfa_required') {
navigate(`/mfa?token=${res.mfa_token}`);
} else {
navigate('/dashboard');
}
}}>
<input name="email" type="email" />
<input name="password" type="password" />
<button type="submit" disabled={login.isPending}>Sign in</button>
{login.error && <p>{login.error.message}</p>}
</form>
);
}

Session

import { useSession } from '@identsphere/react';

function ProtectedPage() {
const { data, isLoading } = useSession();
if (isLoading) return <Spinner />;
if (!data) return <Navigate to="/login" />;
return <p>Hi, {data.user.email}</p>;
}

Permissions

import { useCapabilities } from '@identsphere/react';

function InviteButton() {
const caps = useCapabilities();
if (!caps?.permissions.includes('members.invite')) return null;
return <button>Invite</button>;
}

MFA

import {
useMfaStatus, useMfaSetup, useMfaEnable, useMfaDisable, useMfaChallenge,
} from '@identsphere/react';

function MfaPage() {
const status = useMfaStatus();
const setup = useMfaSetup();
const enable = useMfaEnable();

// step 1: get the QR code
// step 2: prompt user for first code
// step 3: call enable, show recovery codes once
}

Passkeys

import { usePasskeyEnroll, usePasskeyLogin, usePasskeys, useDeletePasskey } from '@identsphere/react';

function PasskeyPage() {
const enroll = usePasskeyEnroll();
return (
<button onClick={() => enroll.mutateAsync({ label: 'YubiKey 5C' })}>
Add passkey
</button>
);
}

The enroll hook drives the entire ceremony (begin → navigator.credentials.create → complete).

Sessions

import { useSessions, useRevokeSession, useRevokeAllSessions } from '@identsphere/react';

function SessionsPage() {
const { data } = useSessions();
const revoke = useRevokeSession();
return (
<ul>
{data?.map(s => (
<li key={s.id}>
{s.user_agent ?? 'Unknown'}{s.is_current ? 'current' : 'other'}
{!s.is_current && (
<button onClick={() => revoke.mutateAsync(s.id)}>Revoke</button>
)}
</li>
))}
</ul>
);
}

Route guards

import { RequireAuth, RequirePermission } from '@identsphere/react';

<Routes>
<Route path="/" element={<Home />} />
<Route
path="/dashboard"
element={<RequireAuth fallback={<Navigate to="/login" />}><Dashboard /></RequireAuth>}
/>
<Route
path="/admin"
element={
<RequirePermission permission="members.remove" fallback={<Forbidden />}>
<AdminPanel />
</RequirePermission>
}
/>
</Routes>

Axios client

For your own HTTP calls to the auth backend (custom endpoints, fetching data that's behind auth), use the included Axios client:

import { useAuthClient } from '@identsphere/react';

function ProfileEditor() {
const client = useAuthClient();
const update = async (patch: ProfilePatch) => {
await client.patch('/v1/users/me', patch);
};
// ...
}

The client handles:

  • credentials: 'include' so cookies travel.
  • X-CSRF-Token injection from the identsphere_csrf cookie.
  • Refresh on 401: catches the 401, POSTs /v1/auth/refresh, retries the original request, surfaces the original error if refresh also fails.

Calling your own backend

For calls to your business API (not the auth backend), use your own client; just ensure credentials: 'include'.

fetch('https://app.example.com/api/projects', {
credentials: 'include',
}).then(r => r.json());

Your backend then validates the JWT (Pattern 2) or proxies through the auth backend (Pattern 1).

Environment variables

VariablePurpose
VITE_AUTH_API_URLOrigin of the auth backend, e.g. https://auth.example.com

Vite, Next.js, and Create React App all accept different env-var prefixes; adapt accordingly.