@drvalue-oss/iam-react
v0.8.0
Published
React hooks + Zustand auth store + axios client for drvalue IAM
Downloads
96
Maintainers
Readme
@drvalue-oss/iam-react
React adapter for drvalue IAM. Provides:
createAuthStore<U>()— typed Zustand auth storeuseTokenRefresh()— schedules background refresh just before expiry, cross-tab synceduseSessionCheck()— periodic + on-focus session validationsetupApiClient()— axios instance with Bearer injection and single-flight 401 refreshconfigureAuth()— pick how tokens are refreshed:bff(default) ordirect(no backend)
Re-exports everything from @drvalue-oss/iam-core so a single import covers most consumers.
Install
pnpm add @drvalue-oss/iam-reactPeer dep: react ^18 || ^19.
Auth modes
Each app picks how it obtains and refreshes tokens. You don't have to call configureAuth at all — the default is bff, which is the historical behavior.
| Mode | Refresh token lives in | Refresh call | Use for |
|---|---|---|---|
| bff (default) | BFF httpOnly cookie | bodyless POST refreshUrl on your origin | Apps with a Backend-for-Frontend (e.g. Next.js + @drvalue-oss/iam-next) |
| direct | browser localStorage | POST {iamServerUrl}/auth/token/refresh with { refresh_token } | SPAs with no backend (plain Vite + React) |
⚠️
directis for internal / low-risk services only. The refresh token becomes readable by JS, so an XSS flaw can exfiltrate it for long-term account takeover. For anything external-facing, run a BFF and usebffmode. See SECURITY.md.
The bff wiring is unchanged — see the Wiring section below. For direct, see Direct mode (no backend).
Wiring
// stores/auth.ts
import { createAuthStore } from '@drvalue-oss/iam-react';
interface MyUser {
id: string;
email: string;
name: string;
companyId?: string;
}
export const useAuthStore = createAuthStore<MyUser>();// lib/api.ts
import { setupApiClient } from '@drvalue-oss/iam-react';
export const api = setupApiClient({
baseURL: process.env.NEXT_PUBLIC_API_URL!,
onUnauthorized: () => {
// multi-tier example: admin pages bounce to /admin/login
const isAdmin = window.location.pathname.startsWith('/admin');
window.location.href = isAdmin ? '/admin/login' : '/login';
},
});// app/providers.tsx
'use client';
import { useTokenRefresh, useSessionCheck } from '@drvalue-oss/iam-react';
import { useAuthStore } from '@/stores/auth';
export function AuthProviders({ children }: { children: React.ReactNode }) {
const logout = useAuthStore((s) => s.logout);
useTokenRefresh();
useSessionCheck({
onSessionExpired: () => {
logout();
window.location.href = '/login?reason=expired';
},
});
return <>{children}</>;
}Direct mode (no backend)
For a plain Vite + React SPA with no BFF. Call configureAuth once at boot, then the same store/hooks/client work — refresh and the login code-exchange go straight to the IAM server.
// lib/auth.ts — run once at app boot, before any token use
import { configureAuth } from '@drvalue-oss/iam-react';
configureAuth({
mode: 'direct',
iamServerUrl: import.meta.env.VITE_IAM_SERVER_URL, // https://iam.drvalue.co.kr
});// login button — send the user to IAM, come back to /callback?code=...
import { buildLoginUrl } from '@drvalue-oss/iam-react';
location.href = buildLoginUrl({
iamBaseUrl: import.meta.env.VITE_IAM_SERVER_URL,
callbackUrl: `${location.origin}/callback`,
});// app/routes/Callback.tsx — exchange the code for tokens in the browser
import { useEffect } from 'react';
import { exchangeAuthCode } from '@drvalue-oss/iam-react';
import { useNavigate } from 'react-router-dom';
export function Callback() {
const navigate = useNavigate();
useEffect(() => {
const code = new URLSearchParams(location.search).get('code');
if (code) {
exchangeAuthCode(code, `${location.origin}/callback`)
.then(() => navigate('/', { replace: true }))
.catch(() => navigate('/login?error=auth'));
}
}, [navigate]);
return null;
}// providers — identical to bff mode; the hooks follow the configured mode
import { useTokenRefresh, useSessionCheck, setupApiClient, clearAuthTokens } from '@drvalue-oss/iam-react';
export const api = setupApiClient({ baseURL: import.meta.env.VITE_API_URL });
export function AuthProviders({ children }: { children: React.ReactNode }) {
useTokenRefresh();
useSessionCheck({ onSessionExpired: () => { clearAuthTokens(); location.href = '/login'; } });
return <>{children}</>;
}Notes:
- The IAM server must CORS-allow your SPA origin for
/auth/token/exchange,/auth/token/refresh, and your API — direct mode calls them cross-origin from the browser. useTokenRefresh/useSessionCheckignorerefreshUrlin direct mode (they useiamServerUrl).clearAuthTokens()clears the access token and the stored refresh token. To revoke server-side (all devices), call IAM's revoke from a trusted context — not the browser.
