> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sublay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Built-in Auth

> Email/password sign-up, sign-in, and sign-out flows

Sublay's built-in authentication lets you register and sign in users with an email address and password without building your own auth backend. The SDK's `useAuth` hook exposes all the actions you need.

## Sign Up

Call `signUpWithEmailAndPassword` with at minimum an `email` and `password`. Additional profile fields are optional.

```tsx theme={null}
import { useAuth } from "@sublay/react-js";

function SignUpForm() {
  const { signUpWithEmailAndPassword } = useAuth();

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    try {
      await signUpWithEmailAndPassword({
        email: form.email.value,
        password: form.password.value,
        name: form.name.value,
      });
      // User is now signed in
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" />
      <input name="password" type="password" placeholder="Password" />
      <button type="submit">Sign Up</button>
    </form>
  );
}
```

### Optional Profile Fields

You can pass additional profile data at registration time:

| Field | Type | Description |
| - | - | - |
| `name` | `string` | Display name |
| `username` | `string` | Unique username within the project |
| `avatar` | `string` | URL of avatar image |
| `bio` | `string` | Short bio text |
| `location` | `{ latitude: number; longitude: number }` | Geographic location |
| `birthdate` | `Date` | Date of birth |
| `metadata` | `Record<string, any>` | Public custom data |
| `secureMetadata` | `Record<string, any>` | Private custom data (not returned to other users) |
| `avatarFile` | `File \| Blob` | Upload an avatar image directly |
| `bannerFile` | `File \| Blob` | Upload a profile banner image |

<Note>
  If you supply both `avatar` (URL) and `avatarFile` (file upload), the file
  takes precedence and the URL is ignored.
</Note>

## Sign In

```tsx theme={null}
import { useAuth } from "@sublay/react-js";

function SignInForm() {
  const { signInWithEmailAndPassword } = useAuth();

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    try {
      await signInWithEmailAndPassword({
        email: form.email.value,
        password: form.password.value,
      });
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" placeholder="Email" />
      <input name="password" type="password" placeholder="Password" />
      <button type="submit">Sign In</button>
    </form>
  );
}
```

## The Account Limit

A device stores at most **5 accounts** ([Multi-Account](/v8/sdk/authentication/multi-account)). Both calls above refuse a sixth, throwing an `Error` whose message is exported as `ACCOUNT_LIMIT_MESSAGE`:

```tsx theme={null}
import { useAuth, ACCOUNT_LIMIT_MESSAGE } from "@sublay/react-js";

try {
  await signInWithEmailAndPassword({ email, password });
} catch (err) {
  if (err instanceof Error && err.message === ACCOUNT_LIMIT_MESSAGE) {
    setError("You're signed in to 5 accounts. Sign out of one to continue.");
  } else {
    setError("Incorrect email or password.");
  }
}
```

| | Sign-up | Sign-in |
| - | - | - |
| Checked | Before the request — normally no account is created | After the request, on the account the server resolved |
| Already-stored account | n/a (a sign-up is always new) | **Succeeds** — re-authenticating an account you already store is never blocked |

<Note>
  Sign-in is deliberately checked *after* the network call. Comparing the typed email against the stored accounts first would be faster and wrong: it would refuse users whose stored email is out of date, absent, or capitalised differently, with no way through.
</Note>

<Warning>
  **One sign-up case does leave a user record behind.** The pre-request check can only use the accounts stored when the call left. If the fifth slot fills while your sign-up is in flight — another tab, or a second sign-in racing this one — the account has already been created on the server by the time the limit is discovered. The SDK signs that session straight back out, but the **user record persists**, so retrying the same email after freeing a slot fails with "email already in use". Sign in with it instead.
</Warning>

A refusal leaves the currently active account signed in, leaves the accounts map untouched, signs the server-side session created by the refused attempt back out, and sets `accountLimitReached` — see [Reaching the account limit](/v8/sdk/authentication/multi-account#reaching-the-account-limit).

## Sign Out

`signOut` revokes the current session's entire token family on the server and clears local auth state. Once this device has a stored push identifier it also asks the server to unbind that account's push notifications, in the same transaction as the session teardown.

```tsx theme={null}
import { useAuth } from "@sublay/react-js";

function SignOutButton() {
  const { signOut } = useAuth();

  return (
    <button onClick={() => signOut().catch(showError)}>Sign Out</button>
  );
}
```

`signOut` **rejects** when the server refuses that unbind, and in that case nothing was torn down on either side — the session survives so the user can retry. Its signature is `() => Promise<void>`, so nothing warns you at compile time; without the `catch` the rejection is unhandled and your UI reports a sign-out that did not happen. Every other failure — no network, a throttled or otherwise rejected request, a device that never registered for push — still signs out locally, because a user must always be able to sign out. See [`useAuth`](/v8/hooks/auth/use-auth).

## Change Password

Users who signed up with email/password can change their password while authenticated. The current password must be provided for verification.

**A password change ends every *other* session for that user**, so their other devices must sign in again with the new password. The session making the call survives — the server reads which session is asking off the access token the request already carries, so nothing extra is sent. It also removes that user's push bindings on every other device while keeping this one's, which the hook arranges by sending the device identifier it already holds. See [`useAuth`](/v8/hooks/auth/use-auth) and [Change Password](/v8/api-reference/auth/change-password).

```tsx theme={null}
import { useAuth } from "@sublay/react-js";

function ChangePasswordForm() {
  const { changePassword } = useAuth();

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    try {
      await changePassword({
        password: form.current.value,
        newPassword: form.next.value,
      });
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="current" type="password" placeholder="Current password" />
      <input name="next" type="password" placeholder="New password" />
      <button type="submit">Change Password</button>
    </form>
  );
}
```

## Password Reset

Users who forget their password can request a reset email. Sublay sends a link to the registered email address. The link expires after 1 hour.

Completing the reset revokes **every** session for that user and removes **every** push binding they hold, on every device — including the one they are standing on. They sign in again everywhere, and each device re-binds for push on its own once it is next opened. The reset itself is completed by the page the link points to, which collects the new password and calls the [Reset Password](/v8/api-reference/auth/reset-password) endpoint — there is no React hook for that step.

```tsx theme={null}
import { useRequestPasswordReset } from "@sublay/react-js";

function ForgotPasswordForm() {
  const requestPasswordReset = useRequestPasswordReset();

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    const result = await requestPasswordReset({ email: form.email.value });
    console.log(result.message);
    // Always returns a success message to prevent email enumeration
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" placeholder="Email" />
      <button type="submit">Send Reset Link</button>
    </form>
  );
}
```

<Note>
  The API always responds with a success message regardless of whether the email
  address is registered, to prevent user enumeration.
</Note>

## Auth State

Check whether the SDK has finished initializing and whether a user is currently signed in:

```tsx theme={null}
import { useAuth } from "@sublay/react-js";

function AuthGuard({ children }: { children: React.ReactNode }) {
  const { initialized, accessToken } = useAuth();

  if (!initialized) return <p>Loading...</p>;
  if (!accessToken) return <p>Please sign in.</p>;

  return <>{children}</>;
}
```

## Email Verification

After sign-up, you can prompt users to verify their email address. Sublay sends a short-lived token (5 minutes) via email. There are two delivery modes: a **code** the user types back into your app, or a **link** they click to verify automatically.

### Send a verification email

`mode` is always required, and it determines what else you need to pass:

```tsx theme={null}
import { useSendVerificationEmail } from "@sublay/react-js";

function VerifyEmailPrompt() {
  const sendVerificationEmail = useSendVerificationEmail();

  return (
    <button
      onClick={() =>
        sendVerificationEmail({
          mode: "code",
          tokenFormat: "numeric",
          tokenLength: 6,
        })
      }
    >
      Send verification email
    </button>
  );
}
```

```tsx theme={null}
// 6-digit numeric code — easy for users to type
await sendVerificationEmail({ mode: "code", tokenFormat: "numeric", tokenLength: 6 });

// A code using the full hex charset — tokenLength isn't allowed here, since
// hex ignores it and always produces a fixed 64-character token
await sendVerificationEmail({ mode: "code", tokenFormat: "hex" });

// Clickable link — verifies automatically when opened. tokenFormat/tokenLength
// are optional for links, since the token is never read by a human
await sendVerificationEmail({
  mode: "link",
  redirectUrl: "https://yourapp.com/email-verified",
});
```

| Option | Type | Required | Description |
| - | - | - | - |
| `mode` | `"code" \| "link"` | always | Whether to send a code to enter, or a link to click |
| `tokenFormat` | `"hex" \| "numeric" \| "alpha" \| "alphanumeric"` | when `mode: "code"` (optional, defaults to `"hex"`, for `mode: "link"`) | Character set for the token |
| `tokenLength` | `number` (4–12) | when `mode: "code"` and `tokenFormat` isn't `"hex"` (not allowed when it is `"hex"`; optional for `mode: "link"`, defaults to `6`) | Length of the token |
| `redirectUrl` | `string` | never | Where to redirect after link verification (only valid with `mode: "link"`) |

TypeScript enforces this combination at compile time — `sendVerificationEmail({ mode: "code" })` or `sendVerificationEmail({ mode: "code", tokenFormat: "numeric" })` (missing `tokenLength`) won't type-check.

### Verify with a code

When using `mode: "code"`, collect the token from the user and call `useVerifyEmail`:

```tsx theme={null}
import { useVerifyEmail } from "@sublay/react-js";

function VerifyCodeForm() {
  const verifyEmail = useVerifyEmail();
  const [code, setCode] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await verifyEmail({ token: code });
      // user.isVerified is now true in the local Redux store
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={code}
        onChange={(e) => setCode(e.target.value)}
        placeholder="Enter code"
      />
      <button type="submit">Verify</button>
    </form>
  );
}
```

On success, `user.isVerified` is updated immediately in the local Redux store — no refetch needed.

### Link mode

When using `mode: "link"`, the email contains a button the user clicks. The server verifies the token and either:

* Redirects to the `redirectUrl` you provided with `?verified=true` appended, or
* Renders a hosted success page if no `redirectUrl` was given.

The `user.isVerified` field will be `true` the next time the user's session is loaded (e.g. on next sign-in or token refresh). There is no SDK callback for link-mode verification since it happens outside the app's JS context.

<Note>
  The verification token is valid for 5 minutes and can only be used once.
  Calling `sendVerificationEmail` while a previous token is still valid will
  generate a new token — the old one remains valid until it expires or the new
  one is used.
</Note>

## See Also

* [`useAuth` hook reference](/v8/hooks/auth/use-auth)
* [`useRequestPasswordReset` hook reference](/v8/hooks/auth/use-request-password-reset)
* [`useSendVerificationEmail` hook reference](/v8/hooks/auth/use-send-verification-email)
* [`useVerifyEmail` hook reference](/v8/hooks/auth/use-verify-email)
* [Sign Up API reference](/v8/api-reference/auth/sign-up)
* [Sign In API reference](/v8/api-reference/auth/sign-in)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.