An isomorphic JavaScript client for Faable Auth.
📚 Full documentation at faable.com/docs
BroadcastChannellocalStorage, cookies, or custom)npm install @faable/auth-js
Requires Node.js >=22.8 for development. The published bundle runs in any
modern browser and in Node/SSR environments.
import { createClient } from '@faable/auth-js'
export const auth = createClient({
domain: '<faableauth_domain>',
clientId: '<client_id>',
redirectUri: window.location.origin
})
// Trigger a social login
await auth.signInWithOauthConnection({ connection: 'google' })
createClient(config) accepts:
| Option | Type | Description |
|---|---|---|
domain |
string |
Required. Your Faable Auth tenant domain. |
clientId |
string |
Required. Application client ID. |
redirectUri |
string |
Default callback URL. Falls back to window.location.origin. |
scope |
string |
Space-separated scopes. Defaults to openid profile email. |
storage |
SupportedStorage |
Custom storage adapter. Defaults to localStorage. |
storageKey |
string |
Prefix for the storage key. Final key is ${storageKey}-${clientId}. |
cookieOptions |
CookieOptions |
When set, switches storage to the cookie adapter. |
lock |
LockFunc |
Custom locking primitive for concurrent refreshes. |
debug |
boolean |
Enables verbose logging. |
// Use the default connection configured on the tenant
await auth.signInWithOauthConnection({})
// Or pick a specific provider (by name or connection_id)
await auth.signInWithOauthConnection({
connection_id: 'conn_01HX…', // preferred when known; falls back to `connection` for legacy tenants
redirectTo: 'https://app.example.com/callback',
scopes: 'openid profile email',
queryParams: { prompt: 'select_account' }
})
In browsers the SDK uses the PKCE flow by default and exchanges the code for a
session on the callback page. The first call to createClient automatically
processes the URL when the user lands back on the redirect target.
await auth.signInWithUsernamePassword({
username: 'user@example.com',
password: '••••••••',
redirectTo: 'https://app.example.com/callback'
})
// Step 1 — request a code or link
await auth.signInWithPasswordless({
email: 'user@example.com',
type: 'code' // or "link"
})
// Step 2 — complete the login with the OTP the user received
const { data, error } = await auth.signInWithOtp({
username: 'user@example.com',
otp: '123456'
})
await auth.changePassword({ email: 'user@example.com' })
await auth.signOut() // global — all sessions for this user
await auth.signOut({ scope: 'local' }) // only this device
// Get the current session (refreshes if needed)
const {
data: { session }
} = await auth.getSession()
// Subscribe to auth events
const {
data: { subscription }
} = auth.onAuthStateChange((event, session) => {
// event: INITIAL_SESSION | SIGNED_IN | SIGNED_OUT | TOKEN_REFRESHED | PASSWORD_RECOVERY | USER_UPDATED
})
// Stop listening
subscription.unsubscribe()
// Force a refresh
await auth.refreshSession()
Auth events are broadcast across tabs using BroadcastChannel, so a sign-in or
sign-out in one tab is reflected in every other tab using the same storageKey.
Refresh tokens are sensitive: anyone who reads them can impersonate the user until the token is revoked. The storage you pick decides where they live:
localStorage (default) — simple and supports cross-tab sync via
BroadcastChannel, but any script running on the same origin can read it. A
single XSS lets an attacker exfiltrate the refresh token. Acceptable for
low-risk apps and prototypes; not recommended when the surface has third-party
scripts, user-generated HTML, or strict compliance requirements.Secure, SameSite, and
Domain. Note that this library writes cookies from JavaScript, so they
cannot be marked HttpOnly; an XSS can still read them, but cookies make CSRF
and same-site policies enforceable in a way localStorage does not.If your app is exposed to untrusted content, prefer cookies with Secure: true
and SameSite: "Lax" (or "Strict"), and treat XSS prevention (CSP, escaping,
framework guarantees) as a hard requirement regardless of which adapter you
pick.
Used automatically in browsers. No configuration required.
Useful for SSR setups where the server must read the session from the request.
import { createClient } from '@faable/auth-js'
export const auth = createClient({
domain: '<faableauth_domain>',
clientId: '<client_id>',
storage: 'cookie'
})
That's it. The adapter sets sensible defaults: Path=/, SameSite=Lax, auto
Secure on HTTPS, and a 30-day Max-Age so users stay signed in across browser
restarts.
Use cookieOptions only when you need to override something — e.g. share the
session across subdomains:
createClient({
domain: '<faableauth_domain>',
clientId: '<client_id>',
storage: 'cookie',
cookieOptions: { domain: '.example.com' }
})
Provide any object that implements getItem, setItem, and removeItem (sync
or async). Set isServer: true if values may come from an untrusted source such
as request cookies.
const memoryStorage = {
store: new Map<string, string>(),
getItem: (k: string) => memoryStorage.store.get(k) ?? null,
setItem: (k: string, v: string) => void memoryStorage.store.set(k, v),
removeItem: (k: string) => void memoryStorage.store.delete(k)
}
createClient({ domain, clientId, storage: memoryStorage })
Use cookie storage on the client, then read the session from next/headers on
the server:
// app/page.tsx
import { cookies } from 'next/headers'
import { getSessionFromCookies } from '@faable/auth-js'
export default async function Page() {
const session = getSessionFromCookies(cookies(), { clientId: '<client_id>' })
if (!session) return <SignIn />
return <Dashboard user={session.user} />
}
Pass the same clientId you used in createClient. If you also passed a custom
storageKey to createClient, mirror it here as { clientId, storageKey } so
the helper looks at the same cookie.
For the full guides, API reference, and dashboard setup walkthroughs visit faable.com/docs.
See LICENSE.md.