React Integration
⚡ 12 min readVite + React SPA with redirect-based Transcodes auth — CDN script, transcodes.redirectToSignIn(), callback exchange, client-side route guards.
Canonical walkthrough: Signin · API details: Redirect API
Browser only. The SDK uses IndexedDB and WebAuthn. Do not import or call
transcodes during SSR. If you share components with Next.js, mark them 'use client'.
Before you start
| Requirement | Where |
|---|---|
| Project ID | Console → Authentication Kit → Installation Guide |
| CDN script snippet | Same panel — paste into index.html |
transcodes.d.ts | Console → Project Setup → download TypeScript definitions |
| SDK Redirect Origins | Console → Configuration → Domains — required when sign-in returns to your origin with ?sid= |
| At least one member | Console → RBAC → Users — end users sign in as members |
If redirectUri / callback URL is not registered, MFA succeeds but the return
redirect fails. Match Step
1.
Load the SDK (CDN)
Paste the Console snippet into index.html before your app bundle:
<script
src="https://cdn.transcodes.link/%VITE_TRANSCODES_PROJECT_ID%/webworker.js"
defer
></script>VITE_TRANSCODES_PROJECT_ID=proj_abc123xyzVite replaces %VITE_*% in index.html at build time. After load, window.transcodes exposes redirectToSignIn, handleSignInCallback, token, on, …
Add transcodes.d.ts to your project (e.g. src/types/) and include it in tsconfig.json.
Sign-in flow
1. Start sign-in (button)
export function SignInButton() {
return (
<button type='button' onClick={() => transcodes.redirectToSignIn()}>
Sign in
</button>
);
}Optional explicit callback:
transcodes.redirectToSignIn({
redirectUri: `${window.location.origin}/auth/callback`,
});2. Handle callback on every app load
Safe to run on every page — no session change when ?sid= is absent (success: false, AUTH_CANCELLED).
import { useEffect } from 'react';
export function App() {
useEffect(() => {
void transcodes.handleSignInCallback().then((result) => {
if (result?.success) {
// JWT stored — AUTH_STATE_CHANGED fires
}
});
}, []);
return <Routes />;
}3. Subscribe to auth state
useEffect(() => {
const off = transcodes.on(
'AUTH_STATE_CHANGED',
({ isAuthenticated, member }) => {
setAuthed(isAuthenticated);
setMember(member ?? null);
}
);
return () => off();
}, []);transcodes.token.isAuthenticated() is async — use it for initial render, not sync if (transcodes.token.hasToken()) alone.
Details: Step 3: Redirect · Step 4: Events
Auth provider (optional)
import {
createContext,
useContext,
useEffect,
useState,
type ReactNode,
} from 'react';
type AuthContextValue = {
isAuthenticated: boolean;
isLoading: boolean;
signIn: () => void;
signOut: () => Promise<void>;
};
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
void transcodes.handleSignInCallback();
void transcodes.token.isAuthenticated().then((v) => {
setIsAuthenticated(v);
setIsLoading(false);
});
return transcodes.on(
'AUTH_STATE_CHANGED',
({ isAuthenticated: authed }) => {
setIsAuthenticated(authed);
}
);
}, []);
return (
<AuthContext.Provider
value={{
isAuthenticated,
isLoading,
signIn: () => transcodes.redirectToSignIn(),
signOut: () => transcodes.token.signOut(),
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}Protected routes (React Router)
import { Navigate } from 'react-router-dom';
import { useAuth } from '@/context/AuthContext';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) return null;
if (!isAuthenticated) return <Navigate to='/login' replace />;
return children;
}Step-up and Console
Member must already be signed in (SDK session / Bearer token).
const res = await transcodes.redirectToStepUp({
resource: 'documents',
action: 'delete',
});
const gate = res.payload[0];
const ok =
res.success &&
(gate?.decision === 'allow' ||
(gate?.decision === 'stepup' && gate?.status === 'verified'));
if (ok) {
// run sensitive action
}
transcodes.redirectToConsole();Call your backend with the member JWT
const token = await transcodes.token.getAccessToken();
await fetch('/api/me', {
headers: { Authorization: `Bearer ${token}` },
});Verify on the server with ES256 + project JWK — Step 5: Server-side JWT.
Common mistakes
| Mistake | Fix |
|---|---|
if (transcodes.token.isAuthenticated()) without await | Always await transcodes.token.isAuthenticated() |
| SDK in Server Components / SSR | Client only — useEffect or 'use client' |
| Callback URL not in SDK Redirect Origins | Register origin in Console |
| Step-up before sign-in | redirectToStepUp requires active session |
Calling transcodes before script loads | Wait for DOMContentLoaded or user gesture |