@faable/auth-js - v3.1.0
    Preparing search index...

    Class FaableAuthClient

    The main entry point of the SDK: an isomorphic client bound to a Faable Auth tenant that drives every authentication flow.

    Prefer creating it through the createClient factory in app code. Once instantiated it begins loading its session in the background, so you can subscribe to auth-state and trigger sign-ins right away. The most common starting points are the sign-in methods and getSession.

    Hierarchy

    • Base
      • FaableAuthClient
    Index

    Constructors - Getting started

    Properties

    audience?: string
    domainUrl: string
    redirectUri: string
    scope?: string
    sessionCheckExpiryDays: number
    tokenIssuer: string

    Accessors - Sessions

    • get session(): Session | null

      Cached session value last persisted by the client.

      This is a synchronous accessor that returns whatever the client has already loaded into memory. It can lag behind storage (for example, across tabs before the broadcast event arrives) and never triggers a refresh. Prefer FaableAuthClient.getSession when you need an authoritative value, especially on the server.

      Returns Session | null

      The cached Session or null if no user is signed in.

    Accessors - Lifecycle

    • get lastInitializeResult(): InitializeResult | null

      The result of the most recent FaableAuthClient.initialize run, or null while the first one is still in flight.

      The constructor starts initialize() in the background and discards the promise; this accessor lets code that cannot await it (for example a React provider effect) read whether the last OAuth/redirect attempt errored, instead of being stuck with a session that silently stays null.

      Returns InitializeResult | null

    Accessors - Other

    • get appState(): unknown

      Lo que la app pidió llevarse al arrancar el login (SignInWithOAuthConnection.appState), ya deserializado, o undefined si no pidió nada.

      Es la misma información que appState en el resultado de FaableAuthClient.handleRedirectCallback, pero legible de forma síncrona, que es lo que hace falta dentro de un FaableAuthClient.onAuthStateChange:

      auth.onAuthStateChange((ev) => {
      if (ev === 'SIGNED_IN') console.log(auth.appState)
      })

      ⚠️ No llames a handleRedirectCallback() dentro de un listener: los callbacks se esperan y ese método es el initialize() que los está emitiendo, así que la promesa no resuelve nunca.

      Returns unknown

    Methods - Sign in

    • Forgets the pending code ("Change email", "Use another account").

      Returns Promise<void>

    • Returns the login method the user last completed on this browser, or null when none was recorded — for login UIs that want to show a PostHog-style "Last used" hint next to the matching sign-in button.

      A method is recorded only when the login actually establishes a session (the OAuth/password redirect round-trip finishing, or an OTP exchange succeeding); a clicked button whose flow was abandoned or failed leaves no trace. The record survives FaableAuthClient.signOut on purpose and lives in a dedicated cookie, so it can be shared across subdomains via the lastUsedCookie.domain client config.

      Returns Promise<LastUsedLoginMethod | null>

      The last confirmed method (oauth logins carry the connection / connection_id they were started with) or null.

      const last = await auth.getLastUsedLoginMethod()
      if (last?.method === 'oauth') highlight(last.connection_id)
    • Links an additional OAuth identity (e.g. GitHub) to the CURRENTLY signed-in user — the identity round-trip proves control of the provider account and attaches it to the session user; it never signs in, never creates a user, and never touches the user's email or profile.

      This is NOT signInWithOauthConnection: a sign-in with an unknown provider identity creates a brand-new user, which is exactly the duplicate-account hole "connect" buttons used to fall into. Requires an active session (the server returns error=login_required otherwise) and must not be combined with prompt: 'login' (it would destroy the session being linked to).

      On failure the server redirects back with ?error=... — read it with getRedirectError after the round-trip.

      Parameters

      Returns Promise<OAuthResponse>

      await auth.linkOauthConnection({
      connection_id: GITHUB_CONNECTION_ID,
      redirectTo: window.location.href
      })
    • Records a code the user is waiting for that this browser did not ask for — typically one whose email linked back to your resumeUrl from another device. Same lifecycle as the one signInWithPasswordless writes.

      Parameters

      Returns Promise<void>

    • Finishes a sign-in that a second-factor policy interrupted.

      Call it with the mfa_token from an AuthMfaRequiredError and the code the user typed — six digits from their authenticator app, or one of their recovery codes.

      A wrong code does NOT consume the token: prompt again with the same one instead of restarting the sign-in. The token does expire (a few minutes), after which the flow has to start over.

      Parameters

      • data: {
            audience?: string;
            code: string;
            mfa_token: string;
            type?: "otp" | "recovery_code";
        }
        • Optionalaudience?: string
        • code: string
        • mfa_token: string
        • Optionaltype?: "otp" | "recovery_code"

          Defaults to otp — the authenticator app.

      Returns Promise<AuthResponse>

      const { error } = await auth.signInWithOtp({ username, otp })
      if (isAuthMfaRequiredError(error)) {
      const code = await askUserForTheirCode()
      await auth.signInWithMfa({ mfa_token: error.mfa_token, code })
      }
    • Starts an OAuth / social login by redirecting the browser to the tenant's /authorize endpoint for the chosen connection.

      In browsers the SDK redirects the current window unless skipBrowserRedirect is true. On that redirect success path the returned promise never resolves — the pending navigation owns the page, so a loading state you tie to the await stays on until unload instead of flashing back. Pass skipBrowserRedirect: true to get { data: { url } } back and drive the navigation yourself. PKCE is used by default in browsers, falling back to the implicit flow elsewhere. Prefer connection_id when known — the backend resolves it without an extra lookup; connection (by name) is kept for legacy tenants.

      Parameters

      Returns Promise<OAuthResponse>

      // The tenant's hosted login, with every connection the app offers. With a
      // live SSO session it signs the user in without showing anything.
      await auth.signInWithOauthConnection()

      // Redirects the current window; the promise does not resolve on success.
      await auth.signInWithOauthConnection({
      connection: 'google',
      redirectTo: 'https://app.example.com/callback'
      })

      // Or take over the navigation yourself:
      const { data } = await auth.signInWithOauthConnection({
      connection: 'google',
      skipBrowserRedirect: true
      })
      window.location.assign(data.url)
    • Completes a passwordless login by exchanging an OTP code for a session.

      Pair this with FaableAuthClient.signInWithPasswordless called with type: 'code'. On success the new session is persisted to storage and a SIGNED_IN event is broadcast.

      Parameters

      • data: { audience?: string; otp: string; username: string }

        The user identifier and the OTP code they received.

      Returns Promise<AuthResponse>

      const { data, error } = await auth.signInWithOtp({
      username: 'user@example.com',
      otp: '123456'
      })
    • Starts a passwordless login flow by emailing the user either an OTP code or a magic link.

      A code that was sent is remembered until it is spent: read it back with FaableAuthClient.getPendingPasswordless when the screen that asked for it is opened again (another tab, an in-app browser that closed while the user read the email). Any sign-in or sign-out on this client forgets it.

      Parameters

      • data: {
            appState?: unknown;
            audience?: string;
            authParams?: Record<string, string>;
            email: string;
            resumeUrl?: string;
            type: "code" | "link";
        }

        The user's email and the delivery mechanism.

        • authParams: profile for the account a code creates (signup), as the server's auth_params.
        • resumeUrl: a page of yours the code email links back to, for when it is opened where this browser's storage is not. Must be on one of the client's registered origins.
        • appState: kept with the pending code, never sent.

      Returns Promise<AuthResult<any>>

      await auth.signInWithPasswordless({
      email: 'user@example.com',
      type: 'code'
      })
    • Signs the user in with a username + password against a database connection on the tenant.

      The server responds with an HTML form that posts the user back to the tenant's /login/callback to complete the OAuth flow; the SDK auto-submits it from the current document. That is why the success path resolves to { data: null, error: null } — the actual session lands on the redirect target, not on this return value. Subscribe with FaableAuthClient.onAuthStateChange to observe the resulting SIGNED_IN event.

      Parameters

      • data: {
            audience?: string;
            password: string;
            redirectTo?: string;
            state?: string;
            username: string;
        }

      Returns Promise<AuthResult<null>>

      await auth.signInWithUsernamePassword({
      username: 'user@example.com',
      password: '••••••••',
      redirectTo: 'https://app.example.com/callback'
      })
    • Creates a user in the tenant's database connection, and nothing else: no login, no navigation. The browser-side counterpart of Auth0's webAuth.signup() — a contact or waitlist form can register people without a backend of its own.

      Calls the public POST /dbconnections/signup. Which identifier is required follows the connection's login identifier (email, username or either), and the password is optional: a user created without one sets it through the password reset email. Unlike Auth0, which rejects a signup without password, Faable neither generates one nor stores an empty one — the credential just has no password yet.

      The user is created with email_verified: false; any verification / welcome email is driven by the tenant's account settings.

      Parameters

      • data: SignupParams

        The new user's identifier(s), optional password and profile fields.

      Returns Promise<AuthResult<SignupResult>>

      const { data, error } = await auth.signup({
      email: 'user@example.com',
      name: 'Ada Lovelace',
      user_metadata: { source: 'contact-form' }
      })
      if (error?.code === 'email_exists') showAlreadyRegistered()
      else if (error) showError(error.message)
      else console.log('created', data.user_id)
    • Registers a new user and, by default, signs them in.

      Kept for compatibility: with signIn unset or true it is FaableAuthClient.signupAndLogin (the browser navigates on success); with signIn: false it is FaableAuthClient.signup and returns the created user. New code should call one of those two directly.

      Parameters

      Returns Promise<AuthResult<SignupResult>>

      const { error } = await auth.signUp({
      email: 'user@example.com',
      password: '••••••••',
      name: 'Ada Lovelace',
      redirectTo: 'https://app.example.com/callback'
      })
      if (error) showError(error.message) // e.g. 'email_exists', 'signup_disabled'
      // otherwise the browser is already navigating to complete the login
    • Registers a new user and, by default, signs them in.

      Kept for compatibility: with signIn unset or true it is FaableAuthClient.signupAndLogin (the browser navigates on success); with signIn: false it is FaableAuthClient.signup and returns the created user. New code should call one of those two directly.

      Parameters

      Returns Promise<AuthResult<null>>

      const { error } = await auth.signUp({
      email: 'user@example.com',
      password: '••••••••',
      name: 'Ada Lovelace',
      redirectTo: 'https://app.example.com/callback'
      })
      if (error) showError(error.message) // e.g. 'email_exists', 'signup_disabled'
      // otherwise the browser is already navigating to complete the login
    • Creates a user with FaableAuthClient.signup and then signs them in — the counterpart of Auth0's signupAndLogin.

      The login navigates the browser. Like every interactive username/password login in this SDK, it submits a form that round-trips through the auth server, so on success the page redirects to your redirectTo and the live session is delivered there by FaableAuthClient.initialize (and a SIGNED_IN event). This method only returns when the signup or the login fails, or in non-navigating runtimes (e.g. tests). The password is required here: without one there is nothing to sign in with.

      Parameters

      Returns Promise<AuthResult<null>>

      const { error } = await auth.signupAndLogin({
      email: 'user@example.com',
      password: '••••••••',
      redirectTo: 'https://app.example.com/callback'
      })
      if (error) showError(error.message) // e.g. email_exists, weak_password
      // otherwise the browser is already navigating to complete the login

    Methods - Authorize URLs

    • Redirects the current window to the tenant's /authorize endpoint.

      Fire-and-forget: there is no return value because the browser navigates away. The session lands back on your redirectUri, where FaableAuthClient.initialize consumes it on the next page load. A code request from a browser carries a PKCE challenge, whose verifier that landing exchanges the code with — required on a tenant that enforces OAuth 2.1.

      Parameters

      • options: {
            acr_values?: string | string[];
            audience?: string;
            loginMethods?: string | string[];
            queryParams?: { [key: string]: string };
            redirectTo?: string;
            response_type: string;
            scope?: string;
        }

      Returns Promise<void>

      auth.authorize({
      response_type: 'code',
      redirectTo: 'https://app.example.com/callback'
      })
    • Builds the tenant's /authorize URL without redirecting the browser.

      Useful when you need to render a login link, open the page in a popup, or hand the URL to a different runtime (e.g. a webview). For the everyday "click → redirect" flow use FaableAuthClient.authorize or FaableAuthClient.signInWithOauthConnection.

      The redirect target is resolved with this precedence: options.redirectTo → config.redirectUri → window.location.origin.

      Parameters

      • options: {
            acr_values?: string | string[];
            audience?: string;
            connection?: string;
            loginMethods?: string | string[];
            queryParams?: { [key: string]: string };
            redirectTo?: string;
            response_type?: string;
            scope?: string;
        } = {}
        • Optionalacr_values?: string | string[]

          OIDC acr_values — the assurance level this request demands. urn:faable:loa:2 asks for a login that satisfied a second factor, even when the tenant's policy would not have required one. Combine with prompt: 'login' to force a fresh step-up rather than reusing an existing single-factor session.

        • Optionalaudience?: string
        • Optionalconnection?: string
        • OptionalloginMethods?: string | string[]

          The methods the hosted login screen shows for this login — connection ids or names, and 'passkey'. Each must be one the client already offers; the screen keeps the order configured for the client. Use it to open the same client with fewer options, e.g. only the emailed code: loginMethods: ['email']. It narrows the screen; connection skips it for a single connection instead, and the two cannot be combined.

        • OptionalqueryParams?: { [key: string]: string }

          Extra /authorize params merged in as-is — e.g. { prompt: 'select_account' } or { prompt: 'login' } to force account selection / re-authentication even when an SSO session exists.

        • OptionalredirectTo?: string
        • Optionalresponse_type?: string
        • Optionalscope?: string

      Returns string

      The fully-qualified authorize URL.

      const url = auth.buildAuthorizeUrl({
      connection: 'google',
      redirectTo: 'https://app.example.com/callback'
      })
      window.open(url, 'login', 'popup')
    • Builds the tenant's RP-initiated logout URL ({domain}/logout?client_id=…).

      Navigate the browser to it (top-level, not fetch) to end the session and clear the auth server's SSO cookie — the only reliable way to do that from another origin. FaableAuthClient.signOut does this for you by default; use this helper when you want to drive the navigation yourself.

      Parameters

      • options: { idTokenHint?: string; returnTo?: string } = {}
        • OptionalidTokenHint?: string

          The id_token of the session being ended (id_token_hint). FaableAuthClient.signOut passes the stored one; when driving the navigation yourself, take it from FaableAuthClient.getSession.

        • OptionalreturnTo?: string

          Where to send the browser after logout, mapped to the OIDC post_logout_redirect_uri. Must be registered as a logout URL on the client or the server responds 400.

      Returns string

      window.location.assign(auth.getLogoutUrl({ returnTo: 'https://app.example.com' }))
      

      Logout

    • Sends the user through a fresh login that must satisfy a second factor, regardless of the tenant's policy or of an existing single-factor session.

      The pattern for a sensitive action — changing payment details, deleting an organisation — where the application wants a stronger proof than the one the session already carries. Read FaableAuthClient.getAal first: a session that is already 2 needs no step-up.

      Parameters

      • options: { audience?: string; redirectTo?: string; scope?: string } = {}

      Returns Promise<void>

      if ((await auth.getAal()) < 2) {
      auth.stepUp({ redirectTo: window.location.href })
      return
      }

    Methods - Sessions

    • One claim of the current session's access token, or null when there is no session or the claim is absent. Sugar over getClaims.

      Type Parameters

      • T = unknown

      Parameters

      • name: string

      Returns Promise<T | null>

      const station = await auth.getClaim<string>('ciapol.com/station_id')
      
    • The claims of the current session's access token, decoded locally — the standard JWT set plus any custom claim the tenant put there (a connection's claims_mapping, an Action's api.accessToken.setCustomClaim). Refreshes the session first when it has expired, exactly like getSession, so the claims are always those of a live token; custom claims survive refreshes by design.

      claims is null (with no error) when there is no session. The token is decoded, not signature-verified: use the result for UI and routing decisions, never as authorization — that is the resource server's job. Narrow your own claims with the generic parameter.

      Type Parameters

      • T extends Record<string, unknown> = Record<string, unknown>

      Returns Promise<
          | { data: { claims: JwtClaims & T; session: Session }; error: null }
          | { data: { claims: null; session: null }; error: AuthError | null },
      >

      const { data } = await auth.getClaims<{ 'ciapol.com/station_id': string }>()
      const station = data.claims?.['ciapol.com/station_id']
    • Error the auth server returned via redirect on the current page load (?error=access_denied&error_description=..., RFC 6749 §4.1.2.1), or null when the page wasn't reached through a failed auth round-trip.

      Consume-once: the first read clears it (and the params were already stripped from the URL at initialization), so a later router navigation can't re-show a stale failure. Exists because the promise returned by signInWithOauthConnection dies with the top-level navigation — this is the only place the app can learn WHY the round-trip failed (e.g. an action deny like "Signups from this network are currently restricted").

      Returns Promise<RedirectError | null>

      const redirectError = await auth.getRedirectError()
      if (redirectError) showAlert(redirectError.error_description)
    • Returns the session, refreshing it if necessary.

      The session returned can be null if no user is signed in or the last one has logged out.

      IMPORTANT: This method loads values directly from the storage attached to the client. If that storage is based on request cookies (for example, on the server) the values in it may not be authentic and therefore it's strongly advised against using this method and its results in such circumstances — a warning will be emitted when the storage exposes isServer: true. Re-fetch the user with a verified call (or verify the access token yourself) before trusting it.

      Returns Promise<
          | { data: { session: Session }; error: null }
          | { data: { session: null }; error: AuthError }
          | { data: { session: null }; error: null },
      >

      const { data, error } = await auth.getSession()
      if (data.session) console.log(data.session.user)
    • Why the SDK last terminated the session on its own (a refresh rejected by the server — e.g. code: 'user_suspended', a revoked grant), or null when the last sign-out was voluntary or there was none.

      Consume-once, like getRedirectError: the first read clears it so a later visit to the login page can't re-show a stale banner. Persisted in storage (not memory) because the terminating tab usually navigates away — the login page that renders the reason is a fresh document.

      Returns Promise<SignOutReason | null>

      const reason = await auth.getSignOutReason()
      if (reason?.code === 'user_suspended') showSuspendedPanel()
    • A live access token without showing a login screen — or the reason there cannot be one.

      1. Refresh first. The stored refresh token is exchanged at /oauth/token (no navigation). This is the everyday path.
      2. Then prompt=none, as a top-level navigation. When the refresh is refused — no session, or invalid_grant because the session was revoked: a sign-out on another device, an admin revoke, a back-channel logout — the SDK sends the browser through /authorize?prompt=none. The auth server signs the user back in from its SSO cookie without a screen, and the page lands on redirectTo with the user's location in returnTo (see FaableAuthClient.initialize). On this path the promise never resolves: the page is navigating away.

      There is no hidden iframe on purpose: third-party cookie blocking makes an iframe to the auth domain unreliable in every major browser, so the silent round-trip is a real navigation the user does not see.

      When the SSO session is gone too, the server comes back with ?error=login_required. initialize keeps that for one getRedirectError read, and a further getTokenSilently() on the same page load resolves with an AuthLoginRequiredError instead of navigating again — no loops. A network failure or a 5xx during the refresh is not a refusal: it comes back as the retryable error, and the session is kept.

      Parameters

      Returns Promise<
          | { data: { access_token: string; session: Session }; error: null }
          | { data: null; error: AuthError },
      >

      const { data, error } = await auth.getTokenSilently()
      if (error) {
      if (error.code === 'login_required') auth.authorize({ response_type: 'code' })
      return
      }
      fetch('/api/me', { headers: { Authorization: `Bearer ${data.access_token}` } })
    • Does the current session's access token carry these permissions (all of them)? Read from the permissions claim, which the API emits with the access_token_authz dialect; with RBAC on the API, a user token carries the permissions you requested as scopes that the user's roles grant.

      Decoded locally, not verified: use it to show or hide UI. Your API must check the token itself.

      Parameters

      • permission: string | string[]

      Returns Promise<boolean>

      if (await auth.hasPermission('invites:write')) showInviteButton()
      
    • Does the current session's access token carry these roles (all of them)? Read from the roles claim, present when the API enables include_roles_in_access_token. Same caveat as hasPermission.

      Parameters

      • role: string | string[]

      Returns Promise<boolean>

      const isAdmin = await auth.hasRole('admin')
      
    • Subscribes to auth-state changes for this client.

      The callback fires for INITIAL_SESSION once shortly after subscribing (so consumers don't have to special-case "no event yet"), and then for every SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, PASSWORD_RECOVERY, and USER_UPDATED event. Events are broadcast across tabs through BroadcastChannel, so a sign-in or sign-out in one tab reaches every other tab using the same storageKey.

      Parameters

      • callback: (event: AuthChangeEvent, session: Session | null) => void | Promise<void>

        Invoked with the event name and the new session (or null on SIGNED_OUT). Can return a promise — the SDK awaits it.

      Returns { data: { subscription: Subscription } }

      { data: { subscription } } — call subscription.unsubscribe() to stop listening.

      const { data: { subscription } } = auth.onAuthStateChange((event, session) => {
      if (event === 'SIGNED_IN') console.log('Welcome', session?.user.email)
      })
      // later
      subscription.unsubscribe()
    • Forces a new session by exchanging a refresh token regardless of expiry.

      Normally the SDK handles refresh transparently via the auto-refresh ticker; call this only when you need to force an immediate refresh — e.g. right after a server-side action that changed the user's claims. Omit currentSession to reuse whatever FaableAuthClient.getSession returns.

      Parameters

      • OptionalcurrentSession: { refresh_token: string }

        Optional session shape carrying the refresh token to exchange. When passed it must include refresh_token.

      Returns Promise<AuthResponse>

      const { data, error } = await auth.refreshSession()
      
    • Adopts an externally-provided session into the client.

      Decodes the access token to find its expiry; refreshes immediately when already expired, otherwise fetches the user info to round-trip the session. Persists the result and broadcasts SIGNED_IN. An invalid refresh or access token surfaces as error on the returned object.

      Parameters

      • currentSession: { access_token: string; refresh_token: string }

        Minimal session shape — an access token and a refresh token. Other fields are recomputed.

      Returns Promise<AuthResponse>

      // After receiving tokens from a custom server-side handoff
      await auth.setSession({ access_token, refresh_token })
    • Starts an auto-refresh process in the background. The session is checked every few seconds. Close to the time of expiration a process is started to refresh the session. If refreshing fails it will be retried for as long as necessary.

      If autoRefreshToken is enabled in the client config you don't need to call this function, it will be called for you.

      On browsers the refresh process works only when the tab/window is in the foreground to conserve resources as well as prevent race conditions and flooding auth with requests. If you call this method any managed visibility change callback will be removed and you must manage visibility changes on your own.

      On non-browser platforms the refresh process works continuously in the background, which may not be desirable. You should hook into your platform's foreground indication mechanism and call these methods appropriately to conserve resources.

      Returns Promise<void>

      // React Native / Node: drive the refresh loop on focus events yourself
      appState.addEventListener('change', state => {
      if (state === 'active') auth.startAutoRefresh()
      })

    Methods - Account

    • Starts a verified email change for the currently signed-in user.

      The user must be authenticated — the call is made with the session's access token, and the auth server only lets a user change their own email. It creates a verification ticket and emails the user; the change is applied only after they click the link, which the server handles and then redirects to redirect_uri. The current session is unaffected until then.

      Parameters

      • params: {
            new_email: string;
            redirect_uri?: string;
            verification_mode?: "new_only" | "old_and_new";
        }

        The new email plus optional verification policy.

        • verification_mode: 'new_only' verifies just the new address; 'old_and_new' also requires confirming from the old one. When omitted the account's default policy applies.
        • redirect_uri: where the server sends the user after they verify.

      Returns Promise<AuthResult<unknown>>

      const { data, error } = await auth.changeEmail({
      new_email: 'new@example.com',
      redirect_uri: 'https://app.example.com/account'
      })
      // data: { status: 'verification_sent', ticket_id, verification_mode }
    • Triggers a "change your password" email for a database-connection user.

      The current session is unaffected — the user clicks the link in the email and completes the reset on the tenant's hosted pages. The promise resolves once the email has been queued.

      Parameters

      • params: { email: string }

        The user's email address.

      Returns Promise<AuthResult<unknown>>

      await auth.changePassword({ email: 'user@example.com' })
      
    • Deletes the signed-in user's account: their identities, credentials, second factors and memberships go with it. Permanent — ask the user to confirm first.

      On success the local session is removed and SIGNED_OUT is emitted, as in signOut({ scope: 'local' }): the user it belonged to no longer exists. A suspended user is refused (user_suspended).

      Returns Promise<AuthResult<null>>

      const { error } = await auth.deleteAccount()
      if (!error) router.push('/goodbye')
    • How the signed-in user can sign in: whether they have a password, and which connections (Google, GitHub, the password one…) they are linked to.

      Use it to choose between "Set a password" and "Change password" on a profile screen. Made with the session's access token; no provider tokens or profile data come back.

      Returns Promise<AuthResult<UserCredentials>>

      const { data, error } = await auth.getCredentials()
      // data: { has_password: false,
      // connections: [{ id: 'connection_…', name: 'google', type: 'google_oauth2' }] }
    • One custom profile field of the signed-in user (from user_metadata["faable.com/fields"] of the session's user), or null when it is not set or nobody is signed in. Reads the session, no request.

      Parameters

      • key: string

      Returns Promise<string | null>

      const card = await auth.getProfileField('invite_card')
      
    • The signed-in user's active sessions — every browser, app and CLI login — newest first, and which one is this. For a "Where you're signed in" list. End the others with signOut({ scope: 'others' }).

      Returns Promise<AuthResult<UserSessions>>

      const { data, error } = await auth.listSessions()
      // data: { results: [{ id: 'session_…', user_agent: '…', ip: '…', … }],
      // current_session_id: 'session_…' }
    • Ends every other session of the signed-in user — other browsers, apps and CLI logins — and keeps this one. Same as signOut({ scope: 'others' }), plus how many were ended. Their refresh tokens are refused on next use; access tokens already issued keep working until they expire.

      Returns Promise<AuthResult<{ revoked: number }>>

      const { data, error } = await auth.revokeOtherSessions()
      // data: { revoked: 2 }
    • Sets or changes the signed-in user's password, without leaving the app.

      • Changing an existing password needs current_password.
      • Setting the first one (a user who signed up with Google, say) needs a verified email and a recent sign-in: within 5 minutes and, if the user has a second factor, one that completed it. Otherwise the error code is reauth_required: send the user through FaableAuthClient.authorize with max_age=0 and call again with the new session.

      The connection's password policy applies (password_too_weak). Every other session of the user is signed out; this one stays. The user gets a security email.

      Parameters

      • params: { current_password?: string; password: string }

        password: the new password.

        • current_password: required when the user already has one.

      Returns Promise<AuthResult<{ revoked_sessions: number }>>

      const { data, error } = await auth.setPassword({ password: newPassword })
      if (error?.code === 'reauth_required') {
      // Back here after a fresh sign-in, then retry.
      await auth.authorize({ queryParams: { max_age: '0' } })
      }
      // data: { revoked_sessions: 2 }
    • Unlinks one of the signed-in user's social or enterprise accounts (their Google, GitHub, GitLab…), with the provider tokens it held. Pass the connection id from FaableAuthClient.getCredentials.

      Refused with last_sign_in_method when it would leave the user with no way to sign in: no password, no account on another connection and no passwordless email sign-in. Offer to set a password (FaableAuthClient.setPassword) or link another account first. The user's sessions stay.

      Parameters

      • connection_id: string

        the connection to unlink (connection_…).

      Returns Promise<AuthResult<{ unlinked: number }>>

      const { data, error } = await auth.unlinkIdentity('connection_…')
      if (error?.code === 'last_sign_in_method') {
      // "Set a password first"
      }
      // data: { unlinked: 1 }
    • Updates the signed-in user's own profile: name, company_name, country_iso and the custom fields. Keys left out are not touched.

      • country_iso takes an ISO 3166 code (ES or ESP) and is stored as alpha-2; an unknown code fails with bad_request.
      • fields are stored in user_metadata["faable.com/fields"]. Only the keys the application declares (profile_fields on the client, else on the tenant) are accepted; any other key, or a value that does not match its declaration, fails the whole call with invalid_profile_field. Fields are merged by key: keys not named are kept, value: null removes one. The rest of user_metadata is untouched.

      On success the stored session's user is updated in place and USER_UPDATED fires, so getSession() and getProfileField read the new values right away. The access token's claims follow on the next refresh (FaableAuthClient.refreshSession).

      Parameters

      Returns Promise<AuthResult<User>>

      const { data: user, error } = await auth.updateProfile({
      name: 'Ada Lovelace',
      country_iso: 'GB',
      fields: [{ key: 'profile', value: 'teacher' }]
      })
    • Sets one custom profile field of the signed-in user, or removes it with null. Shorthand for updateProfile({ fields: [{ key, value }] }): the key must be declared in the application's profile_fields, or the call fails with invalid_profile_field. Other fields are kept.

      Parameters

      • key: string
      • value: string | null

      Returns Promise<AuthResult<User>>

      const { error } = await auth.updateProfileField('invite_card', 'this-app')
      

    Methods - Sign out

    • Signs the user out and clears the session from storage.

      In a browser context this removes the persisted session and broadcasts a SIGNED_OUT event to every tab listening on the same storageKey. The access token JWT itself remains valid until its exp — keep that expiry short.

      By default ('app' scope) it signs the user out of this app only: its refresh token is revoked at the auth server (RFC 7009) and its storage cleared, with no navigation. The tenant's SSO session stays, so the user's other apps on the same tenant keep theirs, and the next sign-in here comes back without asking.

      To end the SSO session too, pass { scope: 'global' }: in a browser the page navigates to the auth server's /logout (a cross-origin fetch cannot clear that cookie) and returns to returnTo if given; the returned promise does not resolve on that path. { scope: 'global', redirect: false } keeps the legacy fetch-only behaviour, or use FaableAuthClient.getLogoutUrl to drive the navigation yourself.

      Scopes:

      • 'app' (default) — this app only, as above. The revoke is best-effort: the local sign-out happens whatever it answers.
      • 'global' — ends this browser's session at the auth server: every refresh token issued in it is refused from then on, in every app of the tenant, and in a browser the page navigates to /logout to clear the SSO cookie. Sessions on other devices are untouched — that is what 'others' is for.
      • 'local' — only clear this client's storage. Nothing is sent to the server: the SSO session and its refresh tokens stay valid (no redirect).
      • 'others' — POST /me/sessions/revoke-others: ends every session of the user except this one. The local session stays, no SIGNED_OUT event is fired and there is no redirect. Needs a live access token.

      Parameters

      Returns Promise<{ error: AuthError | null }>

      await auth.signOut() // this app only; the user's other apps stay signed in
      await auth.signOut({ scope: 'global' }) // + the SSO session, via /logout
      await auth.signOut({ scope: 'global', returnTo: 'https://app.example.com/bye' })
      await auth.signOut({ scope: 'local' }) // only this device's storage
      await auth.signOut({ scope: 'others' }) // every other device, stay signed in

      Logout

    Methods - Lifecycle

    • Completes an OAuth / magic-link / password-recovery redirect on your callback route and reports the outcome.

      The SDK already consumes the URL during the initialize() it kicks off from the constructor; this is a thin, discoverable wrapper that awaits that same in-flight run (idempotent) so you can:

      • redirect only once the token exchange has finished, and
      • surface error instead of hanging on a "Signing you in…" screen when the exchange fails (e.g. an expired PKCE verifier).

      It also returns returnTo — the app-side destination you optionally passed to signInWith*({ returnTo }) — so you don't need a side channel (like sessionStorage) to remember where to send the user.

      Returns Promise<InitializeResult>

      // app/callback/page.tsx
      const { error, returnTo } = await auth.handleRedirectCallback()
      if (error) showError(error.message)
      else router.replace(returnTo ?? '/')
    • Initializes the client session either from the URL or from storage.

      Automatically called once from the constructor and idempotent — extra calls return the same in-flight promise. Call it explicitly when you need to await an OAuth, magic link, or password-recovery redirect to finish processing so you can surface any returned error.

      Returns Promise<InitializeResult>

      A promise that resolves to { error } — non-null when the URL carried a failure or storage was corrupt; never throws.

      const { error } = await auth.initialize()
      if (error) console.error('Auth redirect failed', error)

    Methods - Session

    • Authenticator Assurance Level of the current session: 2 when it satisfied a second factor (or a passkey that verified the user did both at once), 1 for a single factor, 0 when there is no session.

      Read locally from the access token's acr claim — no network call — so it is cheap enough to gate a render on.

      Returns Promise<0 | 1 | 2>

      if ((await auth.getAal()) < 2) auth.stepUp()
      
    • Did the current session authenticate with this method?

      Values follow RFC 8176 as the server emits them: pwd, otp, hwk for a hardware key, federated for a social login, and mfa once a second factor was satisfied.

      Parameters

      • method: string

      Returns Promise<boolean>

      const usedPasskey = await auth.hasAmr('hwk')