@yugami/identity-client
v0.1.9
Published
Branded OAuth2 client for Yugami Identity. Lightweight PKCE-based auth for browser apps, React hook for session state, and server-side JWT validation.
Downloads
786
Readme
@yugami/identity-client
Branded OAuth2 client for Yugami Identity. Lightweight PKCE-based auth for browser apps, React hook for session state, and server-side JWT validation.
Install
npm install @yugami/identity-client
pnpm add @yugami/identity-clientExports
1. createYugamiClient(config) — Browser actions
Creates the client singleton. All methods return/accept Promise where applicable.
import { createYugamiClient } from "@yugami/identity-client";
const client = createYugamiClient({
identityUrl: "https://api.yugami.in/identity",
clientId: "my-app",
});Methods:
| Method | Returns | Description |
|--------|---------|-------------|
| getToken() | string \| null | Current access token from sessionStorage |
| isAuthenticated() | boolean | Whether an access token exists |
| login(redirectUri) | Promise<never> | Redirects to OAuth authorize page |
| handleCallback() | Promise<boolean> | Exchanges auth code from URL, stores tokens |
| logout() | Promise<void> | Calls POST /logout on server, clears local tokens |
| init() | Promise<void> | Restores session from persisted refresh token (call on page load) |
| refresh() | Promise<string \| null> | Refreshes access token using stored refresh token |
| authFetch(input, init?) | Promise<Response> | Fetch with Bearer injection + 401 auto-refresh |
| getUser() | Promise<UserProfile \| null> | Fetches profile from /profile endpoint |
| onTokenChange(cb) | () => void | Subscribe to auth state changes. Returns unsubscribe function |
Token storage: Uses sessionStorage (survives page refresh, cleared on tab close). Both access and refresh tokens are persisted.
authFetch flow: Injects Authorization: Bearer <token> header. On 401, silently refreshes token using the refresh token, then retries the request once.
// Example: authenticated API call with auto-refresh
const res = await client.authFetch("https://api.example.com/data");
const data = await res.json();2. useYugamiSession(client) — React state hook
Returns session state only. Accepts either a YugamiClient instance or a config object (creates client internally).
import { createYugamiClient, useYugamiSession } from "@yugami/identity-client";
const client = createYugamiClient({ identityUrl, clientId });
function AuthGuard({ children }) {
const { user, isAuthenticated, loading, error } = useYugamiSession(client);
if (loading) return <Spinner />;
if (!isAuthenticated) return <LoginPage onLogin={() => client.login(origin)} />;
return <>{children}</>;
}Returns:
| Field | Type | Description |
|-------|------|-------------|
| user | UserProfile \| null | User profile from /profile endpoint |
| isAuthenticated | boolean | Derived from user !== null |
| loading | boolean | True during initial auth check |
| error | Error \| null | Error from getUser or handleCallback |
What it does on mount:
- Checks URL for OAuth
?code=— exchanges it if present - Calls
client.init()— restores session fromsessionStorage - Fetches user profile via
client.getUser() - Subscribes to
onTokenChange— re-fetches profile when token changes
3. createJwksTokenValidator(opts) — Server-side token verification
Verifies JWTs against the identity service's JWKS endpoint. Supports both symmetric (HS256) and asymmetric (ES256/RS256) keys.
import { createJwksTokenValidator } from "@yugami/identity-client";
const validator = createJwksTokenValidator({
jwksUrl: "https://api.yugami.in/identity/.well-known/jwks.json",
});
// In your request handler:
const result = await validator.verifyToken(token);
if (!result.valid) {
reply.code(401).send({ error: `Token ${result.error}` });
}verifyToken returns:
| Field | Type | Description |
|-------|------|-------------|
| valid | boolean | Whether the token is valid |
| sub | string \| undefined | User ID (subject) |
| email | string \| null | User email |
| sessionId | string \| null | Session identifier |
| error | "expired" \| "invalid" \| undefined | Reason if invalid |
Keys are lazily fetched on first call and cached. Automatically re-fetches if verification fails (keys may have rotated).
4. clientCredentialsGrant(opts) — Backend-to-backend auth
Exchanges client credentials for an access token. Use for service-to-service communication.
import { clientCredentialsGrant } from "@yugami/identity-client";
const tokens = await clientCredentialsGrant(
"https://api.yugami.in/identity/oauth/token",
"my-service",
"client-secret",
"internal",
);Types
interface YugamiClientConfig {
identityUrl: string; // Base URL of Yugami Identity
clientId: string; // OAuth client ID
clientSecret?: string; // Required for confidential clients
storage?: YugamiTokenStorage; // Custom token store (default: sessionStorage)
}
interface UserProfile {
id: string;
email: string | null;
phone: string | null;
emailVerified: boolean;
phoneVerified: boolean;
createdAt: string | null;
displayName: string | null;
avatarUrl: string | null;
}
interface TokenSet {
access_token: string;
refresh_token?: string;
token_type: string;
expires_in: number;
}
interface TokenValidationResult {
valid: boolean;
sub?: string;
email?: string | null;
sessionId?: string | null;
error?: "expired" | "invalid";
}Architecture
┌─────────────────────────────────────────────────┐
│ Browser App │
│ │
│ createYugamiClient() ←── actions │
│ │ login() → redirect to Identity │
│ │ logout() → POST /logout + clear local │
│ │ authFetch() → Bearer + 401 auto-refresh │
│ │ refresh() → silent token refresh │
│ │
│ useYugamiSession(client) ←── state │
│ │ user, isAuthenticated, loading, error │
│ │ (auto-initializes on mount) │
└──────────────┬──────────────────────────────────┘
│
┌─────────▼─────────┐
│ Yugami Identity │
│ (OAuth server) │
└───────────────────┘
┌─────────────────────────────────────────────────┐
│ Backend Service │
│ │
│ createJwksTokenValidator({jwksUrl}) │
│ │ verifyToken(token) → valid/invalid │
│ │ auto-fetches JWKS, handles key rotation │
│ │
│ clientCredentialsGrant(tokenEndpoint, ...) │
│ │ service-to-service auth │
└─────────────────────────────────────────────────┘OAuth Flow (Authorization Code + PKCE)
- App calls
client.login(redirectUri)→ generates PKCE challenge → redirects to Identity - User authenticates on Identity UI
- Identity redirects back to
redirectUri?code=xxx&state=yyy useYugamiSessionon mount detects?code=, callshandleCallback()→ exchanges code for tokens- Tokens stored in sessionStorage, user profile fetched
- Subsequent API calls use
authFetch()— Bearer header + 401 auto-refresh - On page refresh,
init()silently restores session from sessionStorage
