@faable/auth-sdk
v2.7.76
Published
<p align="center"> <a href="https://faable.com"> <h1 align="center">Faable Auth SDK</h1> </a> <p align="center">Client for the FaableAuth REST API — management from a server, session and login flows from a browser or CLI.</p> </p>
Readme
Programmatically manage FaableAuth users, teams, connections and clients through the FaableAuth REST API — and, with the right strategy, call the tenant's session and login endpoints from a browser or a CLI.
⚠️ Where it can run depends on the credential, not on the package. The SDK itself has no Node-only dependencies and bundles for the browser. What you must never ship to a browser or any untrusted runtime is a
client_secretor an API key — those grant management scopes over the whole tenant. A user's own bearer token or the browser's session cookie only grant what that user can do, and those are fine client-side.
| Strategy | Credential | Grants | Where |
| ----------------------- | --------------------------------- | ----------------------------------------------------------------- | --------------------------------------- |
| authClientCredentials | client_id + client_secret | The management scopes of the client (full catalog unless narrowed) | Server only |
| authApikey | API key (fak_…) | The management scopes of the key | Server only |
| authBearer | A token the caller already holds | Whatever that token's scopes and subject allow | Anywhere the token itself may live |
| authCookie | The browser's session cookie | The signed-in user's own session (/me, MFA, passkeys…) | Browser, same-origin with the auth host |
| (none) | — | Public endpoints only (login, passwordless start, device flow…) | Anywhere |
Install
npm install @faable/auth-sdkAuthentication
Auth is explicit and pluggable: you pick an authStrategy and pass its
config in auth. The SDK never reads credentials from the environment —
read them yourself and pass them in. The strategy you choose drives the type of
auth, so the editor autocompletes the right fields. The strategies are
re-exported from @faable/auth-sdk, so you only ever import from this package.
Breaking change (v2): the old
createClientCredentials()/createApikeyAuth()helpers (which readFAABLE_*env vars automatically) were removed. Use theauthStrategy+authpair below.domainis now required — there is no hardcoded default host anymore.
Client credentials (recommended)
authClientCredentials is the default strategy, so you only pass auth
with { client_id, client_secret } — no need to set authStrategy:
import { FaableAuthApi } from "@faable/auth-sdk";
const api = FaableAuthApi.create({
domain: "https://<your-account>.auth.faable.link",
auth: {
client_id: process.env.FAABLEAUTH_CLIENT_ID!,
client_secret: process.env.FAABLEAUTH_CLIENT_SECRET!,
},
});By default the token is requested from <domain>/oauth/token. If your API host
is not the auth server, override it with auth.domain:
auth: {
client_id: "...",
client_secret: "...",
domain: "https://<your-account>.auth.faable.link", // where /oauth/token lives
}API key
import { FaableAuthApi, authApikey } from "@faable/auth-sdk";
const api = FaableAuthApi.create({
domain: "https://<your-account>.auth.faable.link",
authStrategy: authApikey,
auth: { apikey: "fak_xxxxxxxxxxxxxxxx" },
});Bearer token
For a token you already hold — a user's access token in a CLI, a token minted
elsewhere. The SDK does not mint or refresh it; whoever owns the token does.
token may be a getter (sync or async): it is consulted on every request,
so a getter that returns the current token keeps a long-lived client correct
when the token rotates.
import { FaableAuthApi, authBearer } from "@faable/auth-sdk";
const api = FaableAuthApi.create({
domain: "https://<your-account>.auth.faable.link",
authStrategy: authBearer,
auth: { token: () => session.accessToken },
});What the calls may do is whatever the token's subject and scopes allow — a user token reaches the user's own resources, a management token reaches the management API.
Session cookie (browser)
For pages served under the tenant's auth host that authenticate with the
browser's session cookie (the hosted login pages: /me, MFA, passkeys). No
config: the browser attaches the cookie itself; the strategy makes the request
withCredentials so it also does cross-origin, where the auth server must
allow the page's origin for credentials.
import { FaableAuthApi, authCookie } from "@faable/auth-sdk";
const api = FaableAuthApi.create({
domain: "https://<your-account>.auth.faable.link",
authStrategy: authCookie,
});No credentials
Omit auth and authStrategy and the client is anonymous — right for the
public endpoints (login, /passwordless/start, the device flow, OIDC
discovery). Those are not on the generated client; call them through the
fetcher:
const api = FaableAuthApi.create({ domain: "https://<your-account>.auth.faable.link" });
await api.fetcher.post("/passwordless/start", { email, client_id });Identifying your integration
Every request carries x-faable-client: auth-sdk/<version>+<sha> so the
auth server can attribute traffic. An app that wraps the SDK can report as
itself instead — the value you pass replaces the default, on data calls and on
the token request alike:
FaableAuthApi.create({ domain, clientInfo: { client: "my-app/1.4.0", name: "my-app" } });Per-call options
Every fetcher method takes timeout, responseType and retry for that one
call. Retries only ever apply to GET (a replayed write could duplicate a
resource), and timeout and retry multiply — a short deadline on a hot path
wants retry: false next to it:
await api.fetcher.get(`/user/${id}`, { timeout: 800, retry: false });Constructor options
| Option | Type | Description |
| -------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| domain | string | Required. The account host to target, e.g. https://<your-account>.auth.faable.link. No default. The protocol is optional — <your-account>.auth.faable.link and https://<your-account>.auth.faable.link are equivalent. |
| authStrategy | strategy builder | Auth strategy (authClientCredentials | authApikey | authBearer | authCookie), re-exported from @faable/auth-sdk. Defaults to authClientCredentials when auth is given; anonymous when neither is. |
| auth | config | Config for the chosen strategy. Its shape is inferred from authStrategy (e.g. { client_id, client_secret }). |
| debug | boolean | Enables verbose logging in the underlying fetcher. |
| headers | { project_id?; account_id? } | Advanced / testing escape hatch — target domain but have the server resolve a different project/account (sent as x-faable-project / x-faableauth-account). team_id is a deprecated alias of project_id: a team_<hex> value is sent as project_<hex>, and passing both with different ids throws. The SDK no longer sends x-faable-team. |
| clientInfo | { client?; name?; instance? } | How this integration identifies itself (x-faable-client / x-faable-instance). client replaces the SDK's own auth-sdk/<version>+<sha>. |
Usage
Users
// Get a user
const user = await api.getUser("user_xxx");
// Create a user
const created = await api.createUser({
email: "[email protected]",
password: "•••••••••",
});
// Update a user
const updated = await api.updateUser("user_xxx", { phone: "+34XXXXXXXXX" });
// List users (paginated) — first page of 30
const firstPage = await api.listUsers().first();
// Filter by email
const matches = await api.listUsers({ email: "[email protected]" }).first();
// Iterate every page
for await (const page of api.listUsers()) {
for (const user of page) {
// ...
}
}User metadata
const metadata = await api.getUserMetadata("user_xxx");
await api.setUserMetadata("user_xxx", {
onboarded: true,
plan: "pro",
});Teams
const team = await api.getTeam("team_xxx");
const newTeam = await api.createTeam({ name: "Acme Inc." });
await api.deleteTeam("team_xxx");
const myTeams = await api.listTeams({ user_id: "user_xxx" }).first();Team members
const members = await api.listTeamMembers({ query: "alice" }).first();
const isMember = await api.isUserMemberOfTeam("user_xxx", "team_xxx");Connections
const connections = await api.listConections().first();
const connection = await api.getConection("conn_xxx");Clients
const clients = await api.listClients().first();
const client = await api.getClient("client_xxx");Current account
const account = await api.currentAccount();Pagination
List methods return a paginator. Use .first() to grab the first page or iterate with for await ... of to walk all pages:
const paginator = api.listUsers();
const firstPage = await paginator.first();
for await (const page of paginator) {
// each `page` is an array of items
}Types
All response shapes are generated from the OpenAPI spec and re-exported from the package root:
import type { User, Team, TeamMember, Connection, Client } from "@faable/auth-sdk";License
MIT
