Skip to Content

Signup

Faable Auth lets you build a self-service signup for database (email + password) connections without standing up a backend of your own. A browser-only app can create the account and sign the user in with a single SDK call. For social logins, the callback tells you when an account was just created so you can branch into onboarding.

Client-side email + password signup

Use @faable/auth-js signUp() from a plain client-side form. It creates the user and its password against your tenant’s database connection, then signs them in:

import { createClient } from '@faable/auth-js' const auth = createClient({ domain: 'https://your-tenant.faable.app', clientId: '<your_client_id>' }) const { error } = await auth.signUp({ email: 'user@example.com', password: '••••••••', name: 'Ada Lovelace', redirectTo: 'https://app.example.com/callback' }) if (error) { // e.g. error.code === 'signup_disabled' | 'email_exists' showError(error.message) } // On success the browser is already navigating to complete the login.

[!IMPORTANT] signUp() logs the user in through a redirect. Just like every interactive username/password login in the SDK, the sign-in step submits a form that round-trips through the auth server. On success the browser navigates to your redirectTo, where initialize() delivers the live session and fires a SIGNED_IN event. signUp() only returns synchronously when signup itself fails.

The new user is created with email_verified: false. Whether a verification or welcome email goes out is controlled by your tenant’s account settings (verify_email_auto_send, welcome_email_enabled) — see Welcome email below.

Parameters

FieldRequiredDescription
emailyesThe login identifier for the new account.
passwordyesValidated against the connection’s password policy, hashed server-side.
name, given_name, family_namenoOptional profile fields stored on the user.
user_metadatanoArbitrary key/value metadata stored on the user.
connectionnoConnection name, when the tenant has more than one database connection. Defaults to the tenant’s database connection.
redirectTonoWhere the auto-login lands after the redirect. Defaults to config.redirectUri / the current origin.

Welcome email

With notification_settings.welcome_email_enabled on, every new user receives the built-in welcome email once their address is verified. The default copy is generic (Welcome to <account>, a button to the first entry of callback_hostnames). You can personalise it per tenant with the welcome_email object of the account, through the management API (POST /account/:id) — the whole object is replaced on every update, and null returns to the generic copy.

{ "welcome_email": { "cta_url": "https://app.example.com/onboarding", "body": "Your account is ready. Create your first project from the dashboard.", "social_links": [ { "label": "GitHub", "url": "https://github.com/example" }, { "label": "LinkedIn", "url": "https://www.linkedin.com/company/example" } ] } }
FieldDescription
cta_urlWhere the button sends the user. Defaults to https://<first callback hostname> — point it at your app, not your marketing site, so a fresh user lands somewhere useful.
bodyReplaces the default one-line body. Plain text, sent verbatim in every locale the account enables.
social_linksUp to six { label, url } entries rendered as a “Follow ” block after the button (and one per line in the plain-text part). Empty or absent hides the block.

See Email Sending for the address these come from and how to let users reply.

The subject, greeting and signature stay localised (Welcome to <account> / Bienvenido a <account>), so the email keeps working for tenants that enable more than one locale.

The signup endpoint

Under the hood signUp() calls a public, account-scoped endpoint. You can call it directly (for example from a non-JS client):

POST /dbconnections/signup Content-Type: application/json { "client_id": "<your_client_id>", "email": "user@example.com", "password": "••••••••", "name": "Ada Lovelace" }

Response:

{ "status": "created", "user_id": "user_abc", "email_verified": false }

It creates the user and the database credential in one step. It does not establish a session — sign the user in afterwards (the signUp() helper does this for you).

Authentication & limits

  • The endpoint is public (account-scoped, resolved by host) — no management token required. It can only ever create a new, unverified user.
  • Rate-limited to 5 requests per minute per IP.

Errors

HTTPmessageMeaning
400password policy textThe password fails the connection’s password policy.
403signup_disabledPublic signup is turned off for this connection (see below).
409email_takenA credential with that email already exists in the connection.

In @faable/auth-js these surface as an AuthApiError with a stable code (signup_disabled, email_exists).

Disabling signup

Public signup is on by default. To turn it off for a connection, set disable_signup: true on the database Connection (dashboard or management API):

POST /connection/:connection_id Content-Type: application/json Authorization: Bearer <management_token> { "disable_signup": true }

With signup disabled, POST /dbconnections/signup returns 403 signup_disabled and you can restrict account creation to admins provisioning users via the management API.

Detecting new accounts on the OAuth callback

For social / OAuth logins there is no signup form — the account is created transparently on first login. To let your app tell a first-time login apart from a returning one, the auth server appends ?signup=true to the redirect_uri when the callback just created a new account:

https://app.example.com/callback?code=…&state=…&signup=true

@faable/auth-js lifts this into the result of initialize() / handleRedirectCallback() as is_new_user, and strips the marker from the URL:

const result = await auth.handleRedirectCallback() if (result.is_new_user) { // brand-new account — send them to onboarding, fire a signup analytics event… router.push('/welcome') }

[!NOTE] Only social / OAuth logins signal this today. Passwordless and username/password callbacks always report is_new_user: false. The marker lives only on the callback URL — it is never a claim in the issued tokens.

Next steps

Last updated on