React SDK
React SDK for Guardhouse/OIDC authentication in React 18+ applications.
Install
npm install @guardhouse/react
Package: @guardhouse/react
Repository: github.com/legiosoft/guardhouse-sdk-js
Provider Setup
Wrap your app with GuardhouseProvider:
import { GuardhouseProvider } from "@guardhouse/react";
export function App() {
return (
<GuardhouseProvider
config={{
authority: "https://your-tenant.guardhouse.cloud",
clientId: "your-client-id",
redirectUri: window.location.origin,
audience: "https://api.example.com",
}}
>
<YourApp />
</GuardhouseProvider>
);
}
useAuth Hook
import { useAuth } from "@guardhouse/react";
export function LoginButton() {
const { isAuthenticated, isLoading, user, loginWithRedirect, logout } = useAuth();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) {
return <button onClick={() => loginWithRedirect()}>Sign In</button>;
}
return (
<div>
<span>Welcome, {user?.name ?? user?.email}</span>
<button onClick={() => logout()}>Sign Out</button>
</div>
);
}
API Reference
| Property | Type | Description |
|---|---|---|
isLoading | boolean | Initial auth check in progress |
isAuthenticated | boolean | User has valid session |
user | User | null | OIDC user profile |
error | string | null | Auth error message |
loginWithRedirect(options?) | () => Promise<void> | Start login flow |
logout(options?) | () => Promise<void> | Start logout flow |
getAccessToken() | () => Promise<string | null> | Get valid token (refreshes if expired) |
getAccessTokenSilently() | () => Promise<string | null> | Silent token retrieval |
Protected Routes
ProtectedRoute Component
import { ProtectedRoute } from "@guardhouse/react";
import { Routes, Route } from "react-router-dom";
<Routes>
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
Custom loading state:
<ProtectedRoute onRedirecting={() => <Spinner />}>
<Dashboard />
</ProtectedRoute>
withAuthenticationRequired HOC
import { withAuthenticationRequired } from "@guardhouse/react";
function SettingsPage() {
return <div>Protected Settings</div>;
}
export default withAuthenticationRequired(SettingsPage, {
returnTo: "/settings",
onRedirecting: () => <Spinner />,
});
Call Protected APIs
import { useAuth } from "@guardhouse/react";
export function useApi() {
const { getAccessToken } = useAuth();
const fetchProtected = async (url: string) => {
const token = await getAccessToken();
if (!token) throw new Error("Not authenticated");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
};
return { fetchProtected };
}
Configuration
| Option | Required | Description |
|---|---|---|
authority | Yes | OIDC issuer URL |
clientId | Yes | OAuth client ID |
redirectUri | Yes | Callback URI (must match IdP registration) |
audience | Recommended | API audience for authorization code flow |
scope | No | Default: openid profile email |
logoutRedirectUri | No | Post-logout redirect URI |
onRedirectCallback | No | Called after login callback with appState |
allowOfflineAccessScope | No | Required if requesting offline_access |
debug | No | Enable SDK debug logs |
Login & Logout Options
Login
loginWithRedirect({
scope: "openid profile email custom_scope",
audience: "https://api.example.com",
prompt: "login",
appState: { returnTo: "/dashboard" },
});
Logout
logout({
returnTo: window.location.origin,
federated: true, // Sign out from IdP too
});
Post-Login Redirect
<GuardhouseProvider
config={{
// ...
onRedirectCallback: (appState) => {
if (appState?.returnTo) {
window.location.href = appState.returnTo;
}
},
}}
>
SSR / Next.js / Remix
GuardhouseProvider is client-side only.
"use client";
import { GuardhouseProvider } from "@guardhouse/react";
export function AuthProvider({ children }: { children: React.ReactNode }) {
return (
<GuardhouseProvider config={{ /* ... */ }}>
{children}
</GuardhouseProvider>
);
}
Always check isLoading before rendering protected UI.
Session Storage
- Tokens stored in
sessionStorage(notlocalStorage) - Session key:
gh_oidc_session - Session cleared when browser closes
Troubleshooting
Infinite redirect loop
- Ensure
redirectUrimatches exactly what's registered in your IdP - Check
isLoadingstate before triggeringloginWithRedirect
Token not refreshing
- Add
offline_accessto scope - Set
allowOfflineAccessScope: truein config
User profile is null
- Ensure
openid profile emailscopes are requested - Check
/connect/userinfoendpoint is accessible