@fjordid/react
v0.2.1
Published
React integration for FjordID — <FjordIDProvider> and useAuth() with StrictMode support and automatic redirectUri normalization.
Maintainers
Readme
@fjordid/react
React integration for FjordID — EU-first OIDC/OAuth2 identity platform.
Provides <FjordIDProvider> and useAuth() with first-class support for:
- React 18 / 19 StrictMode — safe double-invoke guard, no "can only be initialized once" errors
- Automatic
redirectUrinormalization — defaults towindow.location.origin, avoiding the keycloak-js footgun where?code=…&state=…callback params end up in the redirect URI - Silent token refresh — handled automatically via
FjordAuth - TypeScript — full type inference, zero
anys
Install
npm install @fjordid/react
# or
pnpm add @fjordid/reactQuick start
1. Wrap your app
// main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { FjordIDProvider } from "@fjordid/react";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<FjordIDProvider
options={{ clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID }}
loadingFallback={<div>Loading…</div>}
>
<App />
</FjordIDProvider>
</StrictMode>,
);2. Add silent-check-sso.html
Create public/silent-check-sso.html:
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>Register it as a redirect URI in the FjordID console:
http://localhost:5173/silent-check-sso.html
3. Use auth in any component
import { useAuth } from "@fjordid/react";
export function UserMenu() {
const { user, authenticated, loading, login, logout } = useAuth();
if (loading) return null;
if (!authenticated) {
return <button onClick={() => login()}>Sign in with FjordID</button>;
}
return (
<div>
<span>Hello, {user?.email}</span>
<button onClick={() => logout()}>Sign out</button>
</div>
);
}API
<FjordIDProvider>
| Prop | Type | Default | Description |
| ----------------- | ------------------ | ----------- | ---------------------------------------------------------------------------- |
| options | FjordAuthOptions | required | FjordID configuration |
| children | ReactNode | required | Your app tree |
| loadingFallback | ReactNode | undefined | Shown while the initial SSO check runs (omit to use loading state instead) |
options.redirectUri defaults to window.location.origin — do not set it to window.location.href, which may include OIDC callback query params after a login redirect.
useAuth()
Returns an AuthContextValue object:
| Property | Type | Description |
| ------------------------ | ------------------------------------ | ------------------------------------- |
| authenticated | boolean | Whether the user is logged in |
| loading | boolean | True during the initial SSO check |
| user | FjordUser \| null | Authenticated user profile |
| login(redirectUri?) | () => void | Redirect to FjordID login |
| logout(redirectUri?) | () => void | Redirect to FjordID logout |
| register(redirectUri?) | () => void | Redirect to FjordID registration |
| getToken() | () => Promise<string \| undefined> | Get a valid Bearer token |
| auth | FjordAuth | Underlying @fjordid/client instance |
Environment variables
VITE_KEYCLOAK_URL=https://auth.fjordid.eu
VITE_KEYCLOAK_REALM=fjordid
VITE_KEYCLOAK_CLIENT_ID=fjid_your_client_idReact StrictMode details
React 18 and 19 run useEffect twice in development to surface side-effect bugs. Raw keycloak-js throws "can only be initialized once" on the second call. @fjordid/react avoids this by:
- Creating the
FjordAuthinstance in auseRef(notuseMemooruseState) so it is never recreated. - Tracking the init call with a separate
useRef(false)that persists across the StrictMode unmount → remount cycle, soauth.init()is called exactly once. - Re-subscribing to auth events on every mount/remount (cleanup is properly hooked up in the effect return).
redirectUri mismatch — the callback URL footgun
A common integration mistake with raw keycloak-js:
// ❌ Wrong — window.location.href contains ?code=…&state=… after a login callback.
// On a second login() call (StrictMode re-render, page reload, etc.) Keycloak
// rejects it because the registered URI is just the origin.
keycloak.login({ redirectUri: window.location.href });
// ✅ Correct — always redirect back to the clean origin.
keycloak.login({ redirectUri: window.location.origin });@fjordid/react (and @fjordid/client) default to window.location.origin for you. If you override options.redirectUri, use the clean form without query parameters.
Protected route example
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "@fjordid/react";
export function ProtectedRoute() {
const { authenticated, loading, login } = useAuth();
if (loading) return <div>Loading…</div>;
if (!authenticated) {
login();
return null;
}
return <Outlet />;
}License
MIT © FjordID
