Skip to Content
SDK v3newAuthenticationOverview

Authentication

AuthClient is the SDK 3.0 sign-in client: email+password, OTP code, OAuth (Google/Apple/…), password reset, and anonymous sessions. It persists the session locally (localStorage / chrome.storage.local / memory), refreshes the access token on a timer, and emits onAuthChange on login, refresh, and logout.

import { AuthClient } from '@monetize.software/sdk/core'; const auth = new AuthClient({ paywallId: 'pw_123' }); await auth.ready(); // wait for session hydration from storage if (auth.getCachedUser()) { // user is already signed in from a previous session }

Principles

  • Single source of truth for the session — AuthClient. Any BillingClient accepts it as an option and automatically attaches Authorization: Bearer <access> to every API request, plus syncs identity from auth.user.
  • Default persistencelocalStorage on web, chrome.storage.local on extensions (auto-detected), in-memory as a fallback. You can supply your own StorageAdapter.
  • Lazy refresh. getAccessToken() refreshes itself when expiry is less than 60s away. A network error returns the current token (still valid); a 401 on refresh signs the user out (refresh token revoked).
  • Refresh deduplication. Parallel getAccessToken() calls share a single inflight refresh request to the backend.

No cookies. SDK 3.0 is deliberately built around Bearer tokens so the same code runs in a Chrome extension, Telegram Mini App and a regular browser without juggling SameSite/partitioned cookies.

Sign-in methods

API at a glance

MethodWhat it doesReturns
signInWithEmail({email, password})Sign in with passwordAuthSession
signUp({email, password})Sign up. With email-confirm enabled returns confirmation_required without a sessionSignUpResult
sendOtp({email})Sends a 6-digit code to the emailvoid
verifyOtp({email, token, type?})Verifies the code → sessionAuthSession
requestPasswordReset({email})Recovery email with a codevoid
updatePassword({password})Updates the password of the signed-in uservoid
signInWithOAuth({provider})OAuth via popup with PKCEAuthSession
startOAuthFlow({provider}) / completeOAuthFlow({state, code})Low-level split of signInWithOAuth for offscreen-architecture (used by @monetize.software/sdk-extension). Most hosts call signInWithOAuth instead.{authorize_url, state} / AuthSession
signInAnonymously({captchaToken?, userMeta?, forceNewAnon?})Anonymous sign-in. Idempotent + resumes the same anon user via stored refresh-token. Parallel calls dedupe.AuthSession
upgradeAnonymousToEmail({email, password, idempotencyKey?})Upgrades an anonymous user to email/password without losing balances. With email-confirm enabled returns confirmation_required.UpgradeAnonymousResult
signOut({forgetAnonymous?})Logout — clears local state first, then best-effort hits the servervoid
revokeAllSessions()”Sign out everywhere” — invalidates ALL of this user’s refresh tokens across every devicevoid
resendConfirmation({email})Resends the confirmation email after signUp with email-confirm. 429 = rate limit (~1/minute)void
refresh()Forces a refresh — usually unnecessary, refresh is lazyAuthSession \| null
getAccessToken()Bearer token for APIs; lazy refresh near expirystring \| null
getCachedSession() / getCachedUser()Sync snapshot, no networkAuthSession \| AuthUser \| null
getLastLogin()Last login method + email from storage. Useful for UI prefill (“Continue as user@example.com via Google”).LastLogin \| null
onAuthChange(cb)Subscription with a discriminated event: INITIAL_SESSION (always first), SIGNED_IN/SIGNED_OUT/TOKEN_REFRESHED/USER_UPDATED/PASSWORD_RECOVERY. Callback: (event, session) => voidunsubscribe
ready()Promise that resolves when the session has hydrated from storagePromise<void>
destroy() / isDestroyed()Tears down storage subscriptions and clears listeners. Call on host teardown (SPA route change, hot reload, tests).void / boolean

Built-in login screen (gate a feature behind sign-in): paywall.openSignin()

You don’t need to build your own login UI. open() always shows the price layout, but PaywallUI also exposes dedicated handles that open the modal straight on the built-in auth screen — email/password + OAuth buttons (per settings.auth_providers), fully styled, localized, in Shadow DOM. No custom form, no provider-icon library needed.

import { PaywallUI } from '@monetize.software/sdk'; const paywall = new PaywallUI({ paywallId: 'pw_123', auth: true }); // Gate a feature behind sign-in — login only, no plans: document.getElementById('login').onclick = () => paywall.openSignin(); // New-account flow (starts on the signup form): document.getElementById('register').onclick = () => paywall.openSignup();

Use this whenever sign-in is a precondition rather than a purchase prompt:

  • A returning customer already bought and just needs to log in so the SDK picks up their subscription.
  • You want to require login before unlocking a feature, while keeping open() reserved for the Upgrade/plans flow.
MethodOpens onNotes
openSignin()signin formThe explicit name. Use for “log in to gate a feature”.
openSignup()signup formRespects allow_signup from the paywall layout — if signup is disabled, falls back to signin.
openAuth()signin formAlias of openSignin(), kept for backwards compatibility.

After successful sign-in the modal closes; Back also closes it (the user came only to log in). The trial doesn’t block these flows. Without auth connected (no managed-auth) they’re a no-op — there’s no one to sign in. Symmetric to openSupport() — a short path to a single task without the full paywall flow.

Wiring with BillingClient

Pass auth to BillingClient — the token and identity will be attached automatically, no getAuthToken callback needed.

import { AuthClient, BillingClient } from '@monetize.software/sdk/core'; const auth = new AuthClient({ paywallId: 'pw_123' }); const billing = new BillingClient({ paywallId: 'pw_123', auth }); await billing.bootstrap(); // identity = auth.user; Bearer attached const user = await billing.getUser();

If you set identity explicitly on BillingClient — after the first onAuthChange event (including INITIAL_SESSION on hydration) auto-sync overwrites it with auth.user. Don’t mix both approaches on the same instance.