Skip to main content

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 runsCredentialWhat it can do
Zyphr (server)your backendzy_live_* + za_sec_*everything, incl. the secret-key user directory
ZyphrClient (client)browser / React Nativeza_pub_* + end-user tokenend-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()
Auto-applied token (SDK ≥ latest)

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 (currentPasswordcurrent_password, newPasswordnew_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);
Use the typed method, not a raw body

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).

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.