Skip to main content

Adding Auth to Your App

Zyphr Auth-as-a-Service lets you add complete user authentication to any application without building it yourself. Your app talks to the Zyphr API, which handles registration, login, sessions, MFA, OAuth, and more for your end users.

┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│ Your App │──────▶│ Zyphr API │──────▶│ End Users │
│ (Frontend/ │◀──────│ │◀──────│ │
│ Backend) │ └─────────────┘ └─────────────┘
└─────────────┘

Two Authentication Models

Zyphr supports two credential models depending on where your auth calls originate:

ModelHeadersUse Case
Server-sideX-Application-Key + X-Application-SecretBackend API calls (registration, login, token management)
Client-sideX-Application-Key only + Authorization: Bearer <token>Frontend calls with a user's access token (profile, sessions)
caution

Never expose your secret key in client-side code. Use the public key + user token model for frontend calls.

Step 1: Create an Application

Applications are containers for your end users. Each application gets its own keys, user pool, and configuration. You can create applications through the Dashboard or the API.

Via Dashboard

  1. Navigate to Applications in the sidebar
  2. Click Create Application
  3. Enter your application name and configure settings
  4. Copy the secret key — it is only shown once
  5. Store credentials in your environment variables

Via API

curl -X POST https://api.zyphr.dev/v1/applications \
-H "Authorization: Bearer YOUR_DASHBOARD_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "My SaaS App",
"session_duration_value": 60,
"session_duration_unit": "minutes",
"refresh_token_duration_days": 30,
"allowed_origins": ["https://myapp.com"],
"redirect_uris": ["https://myapp.com/auth/callback"]
}'

The response includes your keys — save them immediately, the secret key is only shown once:

{
"data": {
"id": "app_abc123",
"name": "My SaaS App",
"public_key": "za_pub_xxxx",
"secret_key": "za_sec_xxxx",
"environments": [
{ "mode": "live", "public_key": "za_pub_live_xxxx", "secret_key": "za_sec_live_xxxx" },
{ "mode": "test", "public_key": "za_pub_test_xxxx", "secret_key": "za_sec_test_xxxx" }
]
},
"meta": {
"warning": "Save all secret keys securely. They will not be shown again."
}
}

See Applications for full CRUD documentation.

Step 2: Configure Your Backend

Store your application credentials as environment variables:

ZYPHR_API_KEY=zy_live_xxxx              # Your Zyphr API key
ZYPHR_APP_PUBLIC_KEY=za_pub_xxxx # Application public key
ZYPHR_APP_SECRET_KEY=za_sec_xxxx # Application secret key

Using the Node.js SDK

import { Zyphr } from '@zyphr-dev/node-sdk';

const zyphr = new Zyphr({
apiKey: process.env.ZYPHR_API_KEY!,
applicationKey: process.env.ZYPHR_APP_PUBLIC_KEY!,
applicationSecret: process.env.ZYPHR_APP_SECRET_KEY!,
});

Using Raw HTTP

All server-side auth requests require both application headers:

Node.js — auth helper
const ZYPHR_BASE = 'https://api.zyphr.dev/v1';

async function zyphrAuth(path, body) {
const response = await fetch(`${ZYPHR_BASE}${path}`, {
method: 'POST',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'X-Application-Secret': process.env.ZYPHR_APP_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
return response.json();
}

Step 3: Register Users

Common mistake — wrong register endpoint

POST /v1/auth/register creates a Zyphr platform account (for the dashboard). To register end users in your application, use POST /v1/auth/users/register. If your response includes account, project, or a JWT with aud: "zyphr:dashboard", you're hitting the wrong endpoint.

Using the SDK

const result = await zyphr.auth.registration.registerEndUser({
registerRequest: {
email: 'jane@example.com',
password: 'SecureP@ss123',
name: 'Jane Doe',
metadata: { plan: 'pro' },
},
});

// result.data.user — the new end user
// result.data.tokens — { access_token, refresh_token, expires_in, token_type }

Using cURL

curl -X POST https://api.zyphr.dev/v1/auth/users/register \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"password": "SecureP@ss123",
"name": "Jane Doe",
"metadata": { "plan": "pro" }
}'
Node.js
const result = await zyphrAuth('/auth/users/register', {
email: 'jane@example.com',
password: 'SecureP@ss123',
name: 'Jane Doe',
metadata: { plan: 'pro' },
});

// result.data.user — the new user object
// result.data.tokens — { access_token, refresh_token, expires_in, token_type }

Registration returns tokens immediately so the user is logged in after signup.

See End User Authentication for password requirements, validation, and error handling.

Step 4: Login Users

curl -X POST https://api.zyphr.dev/v1/auth/users/login \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"password": "SecureP@ss123"
}'
Node.js
const result = await zyphrAuth('/auth/users/login', {
email: 'jane@example.com',
password: 'SecureP@ss123',
});

if (result.data.mfa_required) {
// User has MFA enabled — prompt for TOTP code
// See Step 6 and the MFA docs
const { token, expires_at } = result.data.mfa_challenge;
} else {
// Standard login — store tokens
const { access_token, refresh_token } = result.data.tokens;
}

Login supports optional custom_claims to embed application-specific data in the JWT (max 4KB):

{
"email": "jane@example.com",
"password": "SecureP@ss123",
"custom_claims": { "role": "admin", "org_id": "org_123" }
}
Using claims for authorization? Use the persistent claims store.

Claims supplied on login are per-issuance — you must pass them on every login, and they are not remembered between sessions. If you use claims for authorization decisions (e.g. an admin flag), set them once via the secret-key PUT /v1/auth/users/{user_id}/claims endpoint instead. Stored claims are server-set only (clients can't write them, which is what makes them safe for authz), are embedded automatically into the user's tokens on every login and refresh, and support seamless demotion: updating a user's claims takes effect on their next token refresh without forcing a logout. For an immediate hard cut-off, call POST /v1/auth/sessions/revoke-all. Apps that treat claims as an authorization gate can also set revocation_fail_closed on the application so the revocation check fails closed if the store is briefly unreachable.

See End User Authentication for the complete login flow including error handling.

Step 5: Token Management

Access tokens are short-lived JWTs. Use the refresh token to get new ones:

curl -X POST https://api.zyphr.dev/v1/auth/sessions/refresh \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "refresh_token": "zrt_xxxx" }'
Node.js
const result = await zyphrAuth('/auth/sessions/refresh', {
refresh_token: storedRefreshToken,
});

const { access_token, refresh_token } = result.data.tokens;
// Store new tokens — old refresh token is now invalid

To log out, revoke the session:

curl -X POST https://api.zyphr.dev/v1/auth/sessions/revoke \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "refresh_token": "zrt_xxxx" }'

See End User Authentication for session management, revoke-all, and active session listing.

Step 6: Add Enhancements

Once basic auth is working, you can layer on additional features:

FeatureDescriptionGuide
Magic LinksPasswordless email loginMagic Links
OAuth / Social LoginGoogle, Apple, GitHub, etc.OAuth
Multi-Factor AuthTOTP-based 2FA with backup codesMFA
Password PoliciesConfigurable requirements per appSecurity
Email VerificationVerify user email addressesSecurity
Phone AuthSMS OTP registration and loginSecurity
WebAuthn / PasskeysBiometric / hardware key authSecurity
Account LockoutsBrute-force protectionSecurity
User ImpersonationAdmin debug access (audited)Security

Reacting to auth events (webhooks)

Zyphr emits webhooks for the full end-user lifecycle — all under the user.* namespace (user.created, user.login, user.password_changed, user.email_verified, user.mfa_enabled, user.deleted, and more). See the Auth Events table for the complete list of 12 event types.

Next Steps