@brandwacht/sso-auth-react
v0.0.1
Published
BWH SSO authentication library for React apps
Readme
@brandwacht/sso-auth-react
Drop-in SSO authentication library for React apps in the Brandwacht Huren B.V. ecosystem.
React port of @brandwacht/sso-auth (Angular). Same SSO portal, same backend contract, same sessionStorage keys — so a React app and the existing Angular apps share one SSO session and silently log each other in.
Installation
npm install @brandwacht/sso-auth-react react-router-domPeer dependencies: react ^18, react-dom ^18, react-router-dom ^6.
Import the stylesheet once at your app entry:
import '@brandwacht/sso-auth-react/styles.css';Quick start
1. Wrap your app
// main.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import {
BwhSsoAuthProvider,
BwhSsoLoginPage,
RequireBwhAuth,
type BwhSsoAuthConfig,
} from '@brandwacht/sso-auth-react';
import '@brandwacht/sso-auth-react/styles.css';
import { Dashboard } from './Dashboard';
const ssoConfig: BwhSsoAuthConfig = {
ssoAuthPortalUri: import.meta.env.VITE_SSO_PORTAL_URI,
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
appBaseUrl: import.meta.env.VITE_APP_BASE_URL,
appId: 'my-react-app',
resolveLanding: () => '/dashboard',
};
export function App() {
return (
<BwhSsoAuthProvider config={ssoConfig}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<BwhSsoLoginPage />} />
<Route
element={
<RequireBwhAuth>
<AppLayout />
</RequireBwhAuth>
}
>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Route>
</Routes>
</BrowserRouter>
</BwhSsoAuthProvider>
);
}Define
ssoConfigat module scope (or wrap inuseMemo) — passing a fresh object every render will recreate every callback the library exposes.
2. Read auth state and call your API
import { useBwhSsoAuth, useBwhAuthFetch } from '@brandwacht/sso-auth-react';
function Header() {
const { user, logout } = useBwhSsoAuth();
return (
<header>
<span>{user?.email}</span>
<button onClick={logout}>Sign out</button>
</header>
);
}
function ProfileCard() {
const authFetch = useBwhAuthFetch();
const [me, setMe] = useState<Me | null>(null);
useEffect(() => {
authFetch('/api/me').then((r) => r.json()).then(setMe);
}, [authFetch]);
// ...
}For non-React code paths (e.g. configuring an axios instance at module load):
import axios from 'axios';
import { getStoredBwhAuthToken } from '@brandwacht/sso-auth-react';
const api = axios.create({ baseURL: '/api' });
api.interceptors.request.use((config) => {
const token = getStoredBwhAuthToken();
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});Multi-app SSO across Angular + React
This library is wire-compatible with the Angular @brandwacht/sso-auth library:
| Concern | Shared contract |
| -------------------- | ---------------------------------------------- |
| SSO portal flow | redirect → ?sso_code=… → POST /api/sso/exchange → POST {api}/auth/sso |
| Auth storage | sessionStorage['BWH_SSO_AUTH_STATE'] |
| Silent SSO flag | sessionStorage['bwh_sso_silent_attempted'] |
| Return-URL handoff | sessionStorage['bwh_sso_return_url'] |
| Cross-app logout | parent-domain cookie bwh_sso=1 |
| UI prefs | localStorage['bwh.ui.lang'], bwh.ui.theme.dark |
A user logged in to any Angular app will be silently authenticated in your React app on first visit, and vice versa. Make sure all apps use the same ssoAuthPortalUri and that each app's appId + appBaseUrl is whitelisted in the portal.
API
<BwhSsoAuthProvider config={...}>
Top-level provider that wires up auth state, i18n, and theme.
useBwhSsoAuth()
{
token: string | null;
user: SsoAuthUser | null;
isAuthenticated: boolean;
startSsoLogin(): void;
startSilentSsoLogin(): void;
ssoLogin(code: string): Promise<{ token; user }>;
logout(): void;
updateStoredUser(user: SsoAuthUser): void;
}useBwhAuthFetch()
Returns a fetch-compatible function that injects Authorization: Bearer <token> when authenticated.
<RequireBwhAuth>
Route guard for react-router-dom. Renders children when authenticated, otherwise navigates to the configured loginRoute with ?returnUrl=… preserved.
<BwhSsoLoginPage>
The full pre-built login page. Handles the SSO callback, silent SSO, and renders the dark/light themed UI with EN/NL switcher.
useBwhI18n() / useBwhTranslate()
const { lang, setLanguage, t, registerTranslations } = useBwhI18n();
// or, just the translator:
const t = useBwhTranslate();Use registerTranslations('nl', { 'My key': 'Mijn vertaling' }) to add app-specific strings; they override the built-in dictionary.
useBwhTheme()
const { isDark, toggle, setDark } = useBwhTheme();The theme provider toggles class="dark" on <html>. Pair with Tailwind's darkMode: 'class' if you use Tailwind.
getStoredBwhAuthToken(storageKey?)
Read the persisted JWT directly. For module-level setup outside React.
BwhHttpError
Thrown by the internal SSO HTTP helpers. Has { status, error } matching the shape humanizeBwhAuthError reads.
humanizeBwhAuthError(err, t)
Convert a thrown error from ssoLogin() into a user-readable string. Mirrors the Angular library's error handling exactly.
Configuration reference
interface BwhSsoAuthConfig {
ssoAuthPortalUri: string; // e.g. 'https://oauth-portal.bwh.nl'
apiBaseUrl: string; // e.g. '/api'
appBaseUrl: string; // e.g. 'https://my-app.bwh.nl'
appId: string; // unique id registered in the SSO portal
storageKey?: string; // default 'BWH_SSO_AUTH_STATE'
loginRoute?: string; // default '/login'
ssoCodeParam?: string; // default 'sso_code'
ssoEndpoint?: string; // default '/auth/sso'
resolveLanding: (user: SsoAuthUser) => string | Promise<string>;
}Development
npm install
npm run typecheck
npm run build # tsup → dist/{index.js,index.cjs,index.d.ts,styles.css}