Authentication
Login, refresh, SSO, and token stores with @lunnoa/client.
Use @lunnoa/client auth helpers so Pattern B apps do not reinvent login, refresh, or token storage. Pattern A (API keys) still uses createServerClient or apiKey on the constructor.
Choose a client factory
| Helper | Pattern | Credential |
|---|---|---|
createServerClient({ apiKey }) | A | Workspace API key (lna_…), server / BFF only |
createBrowserClient({ … }) | B | User JWTs in a token store (default: localStorage) |
createServerClient({ accessToken }) | B on server | User JWT held by your backend |
createBrowserClient defaults to localStorage keys accessToken and refreshToken (same keys as the built-in Automate UI). Pass createMemoryTokenStore() for Node scripts or tests.
For Pattern B in the browser, allow your origin with CORS_ALLOWED_ORIGINS on the deployment.
React hooks (@lunnoa/client/react)
Opt-in React helpers for Pattern B portals. Peer dependency: react ≥ 18. For the full hook set (progress, needs-input, agent chat, approvals, entities), see React hooks.
| Export | Role |
|---|---|
LunnoaAuthProvider | Context: pass client={createBrowserClient(...)} or baseUrl (+ optional token store) |
useLunnoaAuth() | { user, status, login, verify2faLogin, loginWithToken, logout, listSsoProviders, getSsoLoginUrl, … } |
useLunnoaClient() | Same LunnoaClient instance for entities, actions.run, watchProgress, … |
useAccessToken() | { getAccessToken, status } for AI SDK chat Authorization headers |
More hooks (useExecutionProgress, useNeedsInput, useAgentChat, useApprovalsInbox, useEntityList) are documented in React hooks.
What lunnoa.auth wraps
| Method | HTTP | Notes |
|---|---|---|
login({ email, password }) | POST /api/auth/login | Public. May return { requires2FA, sessionToken } |
verify2faLogin({ sessionToken, token }) | POST /api/auth/2fa/verify-login | Public. Completes 2FA login |
loginWithToken({ token }) | POST /api/auth/login-with-token | Public. Exchanges SSO / email hidden JWT |
refresh({ refreshToken? }) | POST /api/auth/refresh-token | Public. Returns a new access token (refresh is not rotated) |
me() | GET /api/users/me | Requires Bearer JWT |
logout() | (local only) | Clears the token store |
sso.listProviders() | GET /api/auth/sso/providers | Public discovery |
sso.getLoginUrl(providerId) | — | Builds /api/auth/sso/:id/login |
Successful login helpers write camelCase tokens into the store (accessToken / refreshToken). Wire responses still use access_token / refresh_token.
Auto-refresh on 401
When a tokenStore is configured and autoRefresh is true (default for createBrowserClient), a 401 on an authenticated request triggers one POST /api/auth/refresh-token using the stored refresh token, then retries the original call. If refresh fails, the store is cleared.
You can still call auth.refresh() yourself (for example after catching LunnoaApiError.isUnauthorized when auto-refresh is off).
SSO for custom portals
- Call
auth.sso.listProviders()to render buttons (and to respectssoEnforced/passwordAuthDisabled). - Navigate the browser to
auth.sso.getLoginUrl(providerId). - The IdP callback hits Automate at
/api/auth/sso/:providerId/callback, which redirects toCLIENT_URL/verify-token?token=…(the deployment's configured client URL, not an arbitrary custom origin). - Exchange the hidden token with
auth.loginWithToken({ token }).
Token stores
import {
createMemoryTokenStore,
createLocalStorageTokenStore,
LunnoaClient,
} from '@lunnoa/client';
const lunnoa = new LunnoaClient({
baseUrl,
tokenStore: createMemoryTokenStore(), // or createLocalStorageTokenStore()
});Implement TokenStore yourself when tokens live in httpOnly cookies on your BFF: your store reads/writes via your backend; Lunnoa still receives Bearer JWTs.
Related
- TypeScript client SDK: overview and resource map
- API authentication: API keys, security notes
- Errors: 401 refresh mapping