@optima-chat/agentic-auth
v0.6.0
Published
Optional auth module for Optima chat - OAuth, token management, authenticated HTTP client
Readme
@optima-chat/agentic-auth
Optional auth module for Optima chat — OAuth flow helpers, JWT token management, and an authenticated fetch wrapper.
Install
pnpm add @optima-chat/agentic-authAlso available via npm install @optima-chat/agentic-auth or yarn add @optima-chat/agentic-auth.
Quickstart
import {
TokenManager,
OAuthClient,
createOAuth2RefreshFn,
} from '@optima-chat/agentic-auth';
const tokenManager = new TokenManager({
refreshTokens: createOAuth2RefreshFn({
tokenUrl: 'https://auth.example.com/api/v1/oauth/token',
clientId: 'my-app',
}),
});
const client = new OAuthClient({
tokenManager,
authBaseUrl: 'https://auth.example.com',
clientId: 'my-app',
redirectUri: 'https://app.example.com/callback',
});
await client.sendEmailCode('[email protected]');
await client.verifyEmailCode('[email protected]', '123456');Per-endpoint proxying
Each entry in authEndpoints can be either:
- A string path, which is prepended to
authBaseUrl. Use this for endpoints hosted by the auth service directly. { path, raw: true }(orraw(path)), which is used as-is —authBaseUrlis skipped. Use this when the consumer app hosts a same-origin proxy route (e.g. a Next.js/api/auth/*route adding rate-limiting / i18n error wrapping) or the endpoint lives on a different host.
import { OAuthClient, raw } from '@optima-chat/agentic-auth';
const client = new OAuthClient({
tokenManager,
authBaseUrl: 'https://auth.example.com',
clientId: 'my-app',
redirectUri: 'https://app.example.com/callback',
authEndpoints: {
sendEmailCode: raw('/api/auth/email/send-code'),
verifyEmailCode: raw('/api/auth/email/verify'),
// socialAuthorize, revoke default to /api/v1/... on authBaseUrl
},
});Passing a fully qualified URL (https://...) as a bare string now throws — use raw() for that case.
Social login CSRF protection (strict by default)
socialLogin() always sends a state parameter and persists it in sessionStorage before the redirect. handleCallback() reads and clears that state (single-use), rejecting any callback whose state doesn't match. If you don't supply an explicit state, a cryptographically-random one is generated.
// SDK-initiated flow — CSRF validation is automatic
client.socialLogin('google'); // persists state, redirects
await client.handleCallback(window.location.href); // validates + sets tokens
// Explicit state if you want to carry context through the redirect
client.socialLogin('google', 'return-to=/settings');Strict default: if no state was persisted (e.g. an attacker crafted a callback URL for a victim who never clicked social login), handleCallback throws. This is the point of CSRF protection — without it, the attacker can force login as an attacker-controlled identity.
// OAuth flow initiated OUTSIDE this SDK and CSRF enforced elsewhere
// (e.g. server-assigned state via HTTP-only cookie). Opt-out explicitly:
await client.handleCallback(url, { requireState: false });Follows RFC 6749 §10.12.
