Client-side Auth
The Zyphr SDK ships a client-safe entry point, ZyphrClient, for calling
Auth-as-a-Service directly from a browser or React Native app.
The difference from the server Zyphr client is the credential it holds. The
server client needs a secret API key (and, for auth, an application secret
key) — those must never ship in a client bundle. ZyphrClient is built with only
your publishable application key (za_pub_*), which is safe to embed in a
client app, plus the end user's access token once they've signed in.
The credential boundary
| Where it runs | Credential | What it can do | |
|---|---|---|---|
Zyphr (server) | your backend | zy_live_* + za_sec_* | everything, incl. the secret-key user directory |
ZyphrClient (client) | browser / React Native | za_pub_* + end-user token | end-user auth flows only |
ZyphrClient refuses to construct with a secret-shaped key (za_sec_*,
zy_live_*, zy_test_*) — a guardrail so a secret can't be shipped to a client
by mistake:
import { ZyphrClient, ZyphrClientCredentialError } from '@zyphr-dev/node-sdk';
try {
new ZyphrClient({ applicationKey: 'za_sec_...' }); // secret key
} catch (e) {
// ZyphrClientCredentialError: must be a publishable key (za_pub_*)
}
Quick start
import { ZyphrClient } from '@zyphr-dev/node-sdk';
const zyphr = new ZyphrClient({ applicationKey: 'za_pub_xxx' });
// Sign in
const login = await zyphr.auth.login.loginEndUser({
loginEndUserRequest: { email: 'user@example.com', password: '...' },
});
// Store the access token. Once stored, it is applied automatically to every
// user-scoped call — you no longer need to pass authedRequest() for auth.
zyphr.setAccessToken(login.data.tokens.access_token);
// Call a user-scoped endpoint — the stored token is attached automatically.
const me = await zyphr.auth.profile.getEndUser();
You can also construct the client with an existing session token; it is applied
to user-scoped calls automatically, exactly like setAccessToken:
const zyphr = new ZyphrClient({
applicationKey: 'za_pub_xxx',
accessToken: existingAccessToken, // applied automatically to user-scoped calls
});
const me = await zyphr.auth.profile.getEndUser(); // authenticated, no authedRequest()
Earlier SDK releases required authedRequest() on every user-scoped call to
attach the Bearer token. The stored token (whether set in the constructor or via
setAccessToken) is now applied automatically. authedRequest() still works and
is the way to authenticate a single call as a different user —
authedRequest(otherToken) overrides the stored token for that one call.
ZyphrClient exposes the end-user auth surface (login, registration,
sessions, magicLinks, oauth, phone, emailOtp, passwordReset,
emailVerification, mfa, webauthn, organizations, profile) plus
devices for push-token registration. It does not expose server-only APIs
(emails, templates, webhooks, the user directory) — those require a secret and
belong on your backend.
Session helpers
zyphr.setAccessToken(token); // after login / refresh
zyphr.isAuthenticated; // boolean
zyphr.authedRequest(); // RequestInit with X-Application-Key + Bearer
zyphr.authedRequest(otherToken) // override the stored token for one call
Token storage (where the access/refresh token lives between app launches) and
refresh scheduling are your app's responsibility today — use your platform's
secure storage (e.g. expo-secure-store / Keychain on React Native, an
httpOnly cookie or in-memory store on web). A batteries-included reactive
session provider (useSession, auto-refresh, storage adapters) is on the roadmap;
this page describes the safe primitive you can build on now.
Changing a signed-in user's password
Call the typed changeEndUserPassword method — do not hand-build the request
body. Pass camelCase arguments; the SDK serializes them to the snake_case wire
format the API expects (currentPassword → current_password, newPassword →
new_password):
// The stored access token is applied automatically (see Quick start).
const result = await zyphr.auth.profile.changeEndUserPassword({
currentPassword: 'OldP@ss123',
newPassword: 'NewSecureP@ss456',
});
// Changing the password revokes all existing sessions and returns a fresh token
// pair — store it so the current session stays authenticated.
zyphr.setAccessToken(result.data.tokens.access_token);
The API endpoint expects a snake_case body (current_password / new_password).
When you call the typed SDK method with camelCase arguments, the SDK converts them
for you. Posting { currentPassword, newPassword } directly to the REST endpoint
will be rejected (current_password is required) — that's the wire contract, not
an SDK bug. The change-password call requires a valid end-user access token; make
sure one is set (constructor or setAccessToken) before calling.
Multiple client apps, one user pool
If several client apps (e.g. a mobile app plus web dashboards) authenticate
against the same Zyphr application, each app constructs its own ZyphrClient
with the same publishable key. Sessions are per-user, not per-app, so a user
who signs in on one app and switches accounts on another is handled by the
standard session endpoints (sessions.refresh, sessions.revoke,
sessions.revokeAll).
Mobile deep links (OAuth & magic links)
Custom URL schemes are supported for OAuth and magic-link / password-reset
redirects — e.g. yourapp://verify_email. Register the scheme in your
application's redirect_uris allowlist. The token is appended as a query
parameter on your redirect (yourapp://verify_email?token=...), which your app
reads on deep-link open.
When to use the server client instead
Anything that needs a secret stays on your backend with the server Zyphr
client: sending email/SMS/push as your account, managing templates and webhooks,
and the secret-key user directory (list/search/get/update/delete users, set
trusted custom_claims). In particular, use server-set custom_claims — not the
client-writable metadata field — for authorization decisions.