Quickstarts · react-spa
React SPA — Hub login with `oidc-client-ts`
Wire OAuth2 / OIDC into a React 19 + Vite SPA using the standard browser library. Auth code with PKCE, no client secret.
- react
- vite
- oidc
- spa
- pkce
Tested against:framework: React 19 + Vite 5oidcClient: oidc-client-ts@3.0.0
Prereqs
- React 19 + Vite 5 (or Create-React-App, less recommended)
- A Thoryn account
Step 1 — Register a SPA client in Hub
SPAs use auth-code + PKCE — no client secret. Register accordingly:
hub clients create \
--name "My React SPA" \
--redirect-uri "http://localhost:5173/callback" \
--grant-types authorization_code,refresh_token \
--client-type public \
--pkce required \
--scopes "openid email profile"Step 2 — Install
npm install oidc-client-tsStep 3 — Configure
src/auth.ts:
import { UserManager, WebStorageStateStore } from "oidc-client-ts";
export const userManager = new UserManager({
authority: "https://hub.thoryn.org",
client_id: import.meta.env.VITE_THORYN_CLIENT_ID,
redirect_uri: window.location.origin + "/callback",
scope: "openid email profile",
response_type: "code",
userStore: new WebStorageStateStore({ store: window.localStorage }),
});Step 4 — Wire login + callback
src/App.tsx:
import { useEffect, useState } from "react";
import { userManager } from "./auth";
export default function App() {
const [user, setUser] = useState(null);
useEffect(() => {
if (window.location.pathname === "/callback") {
userManager.signinRedirectCallback().then((u) => {
setUser(u);
window.history.replaceState({}, "", "/");
});
} else {
userManager.getUser().then(setUser);
}
}, []);
if (!user) {
return <button onClick={() => userManager.signinRedirect()}>Sign in</button>;
}
return (
<div>
<p>Hello, {user.profile.name}</p>
<button onClick={() => userManager.signoutRedirect()}>Sign out</button>
</div>
);
}Step 5 — Run it
VITE_THORYN_CLIENT_ID=... npm run devhttp://localhost:5173, click sign in, get redirected to Hub, come back logged in.
What's next
- Hub — How it works
- Recipe: role-or — gate components by claim
Troubleshooting
- PKCE missing:
oidc-client-tsenables PKCE by default. If your Hub registration requires it (it should), this is automatic. - localStorage tokens are XSS-exposed: that's the SPA trade-off. For sensitive data, use a back-end-for-frontend instead.