docs

Ease docs/Concepts/Auth

ease.auth: entering and leaving the system

Sign-up, log-in, OTP delivery, passkey registration, sign-out, and session restore. Everything that flips the SDK between signed-in and signed-out. Once you are signed in, ease.me, ease.contacts, and ease.chat are open for business.

What's there

Every auth verb hangs off ease.auth, called after ease.connect() has bootstrapped the tenant. connect() runs once per Ease instance and resolves which auth method the tenant uses; the rest of this page assumes it has already run.

TypeScript · Web SDK
import { Ease } from '@securegroupchat/web-sdk';

const ease = new Ease({ apiKey: 'YOUR_API_KEY' });
await ease.connect(); // tenant bootstrap, once per instance
  • sendSignupOtp(email): kick off the sign-up OTP delivery. Phone variant: sendSignupOtpByPhone(phone) on tenants configured for phone auth.
  • signUp(email, password, otp, options?): finish sign-up with the OTP from the previous step. Returns a Session bound to the new user. Phone variant: signUpByPhone(...).
  • login(email, password): log in an existing user. Phone variant: loginByPhone(phone, password).
  • registerWithPasskey(handle, otp, options?): sign up with a WebAuthn passkey instead of a password, on a passkey-configured tenant.
  • loginWithPasskey(handle): log in an existing user with their passkey.
  • restoreSession(saved): re-attach a Session persisted from a previous launch.
  • signOut(): clear the session, the local libsignal store, and DM history.

Which pair you call depends on the tenant. Read ease.getTenant()?.authMethod after connect(): email_password and phone_password tenants use signUp and login; email_passkey and phone_passkey tenants use the passkey pair.

The OTP flow

Sign-up is a two-step dance. Send an OTP, then sign up with the OTP plus a password. The OTP is delivered out-of-band (email or SMS, per tenant config) and your UI catches the user typing it into a form.

TypeScript · Web SDK
// Step 1: send the OTP
await ease.auth.sendSignupOtp('alice@example.com');

// Step 2: user reads it from their email, types it into your form…
const session = await ease.auth.signUp(
  'alice@example.com',
  'a-strong-password',
  '123456', // the OTP they typed
  { name: 'Alice' },
);

The OTP expires after the tenant's configured TTL. Resending invalidates the previous OTP; the latest one wins. If a user re-requests a code mid-flow, throw away the previous attempt and accept whatever they type after the second email arrives.

Logging an existing user in is a single call.

TypeScript · Web SDK
const session = await ease.auth.login(
  'alice@example.com',
  'a-strong-password',
);

Both signUp and login return a Session. That object is what you persist; the SDK uses it automatically for every subsequent call.

Passkey auth

On a passkey tenant the user registers a WebAuthn passkey rather than a password. A one-time code still gates the first registration (it proves the email or phone is theirs), and the passkey replaces the password on every login after that.

TypeScript · Web SDK
// Step 1: same OTP send as the password flow
await ease.auth.sendSignupOtpByPhone('+14155550101');

// Step 2: the platform passkey prompt fires inside this call
const session = await ease.auth.registerWithPasskey(
  '+14155550101',
  '123456', // the OTP they typed
  { name: 'Alice' },
);

// Returning users:
const returning = await ease.auth.loginWithPasskey('+14155550101');

A passkey user has no password, so the web SDK encrypts the on-device message keystore with a key derived from the passkey's WebAuthn PRF extension. Passkey auth works on any browser that supports passkeys; creating an account additionally needs PRF (Chrome 117+, Safari 18+, a current Firefox), so registerWithPasskey throws passkey_prf_unavailable on a browser without it. On login without PRF the user is signed in but messaging stays gated.

Persisting and restoring the session

After sign-up or log-in, the SDK saves the returned Session for you. On the next launch, read it back from the static Ease.savedSession getter and hand it to restoreSession once connect() has resolved the tenant.

TypeScript · Web SDK
// On the next launch, after connect resolves the tenant:
const saved = Ease.savedSession;
if (saved) {
  await ease.auth.restoreSession(saved);
}

restoreSession validates the saved token against the server and refreshes it if it has expired. After it resolves, the SDK is signed in.

Web caveat. On web, restoreSession does not rehydrate the libsignal store. That store is encrypted with a password-derived key the SDK never persists. Profile and contacts surfaces work after a restore; messaging needs a fresh login with the password. To wipe the saved session yourself, call Ease.clearSavedSession().

Keeping tokens fresh

Sessions carry short-lived tokens. maintainSession runs an auto-refresh loop for the life of the session, calling your optional callback with each rotated Session so you can persist it. Pass an AbortSignal to stop the loop. When you only need a one-shot refresh, call refreshSession directly.

TypeScript · Web SDK
// Refresh tokens on a loop for the life of the session. Pass an
// AbortSignal to stop it (e.g. on unmount or sign-out).
const controller = new AbortController();
ease.maintainSession((session) => {
  // persist the rotated session for the next launch
}, controller.signal);

// Or refresh once, on demand:
const session = await ease.refreshSession();

Sign out

TypeScript · Web SDK
await ease.auth.signOut();

signOut tears down the session, the local libsignal store, and DM history. After it resolves, the device has nothing identifying the user and nothing plaintext. Safe to call from a "sign out" button.

Why auth is its own namespace

The auth verbs could have folded into ease.me.*. They don't, for a pragmatic reason: you are not yet "me" until signUp or login succeeds, and you stop being "me" once signOut returns. Putting the verbs on me would imply they operate on a "me" that doesn't exist yet, or won't exist after sign-out. A separate namespace reads honestly.

See also