---
title: Clients and sign-in flows
description: Use Better Auth's browser client for authentication, preserve session cookies, and distinguish auth endpoints from RELKIT's generated client.
pagination: false
sidebar:
  icon: panels-top-left
---

Use Better Auth's browser client to sign users up, sign them in, and manage their sessions. Use the
[RELKIT generated client](/docs/modules/http/generated-clients) for your application's function-backed routes.
They serve different APIs; mounting auth does not generate typed RELKIT methods for its raw endpoints.

## Create a browser auth client

> **Before you begin**
>
> First complete the backend's [Better Auth setup](/docs/modules/auth/better-auth). In the separate
> frontend package, install the selected native client SDK and its declaration requirements; these
> commands do not install a backend or enable authentication by themselves.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

Before installing, merge these selected declaration repairs into the existing package manifest, preserving unrelated overrides:

```json
{
  "overrides": {
    "@better-fetch/fetch": "1.3.2"
  },
  "pnpm": {
    "overrides": {
      "@better-fetch/fetch": "1.3.2"
    }
  }
}
```

From this guide's application/package root, install only its selected requirements:

```sh
bun add better-auth@1.7.1
bun add --dev @cloudflare/workers-types@4.20250121.0
```

Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in `dependencies`; build/type tooling belongs in `devDependencies`. Installing an adapter does not supply its optional SDK peers or enable the feature's configuration.

For RELKIT's React convenience leaf, use the separate [React authentication setup](/docs/modules/client/authentication).

That setup includes a copyable `AuthProvider` connecting the native session hook to RELKIT's
private query cache. Start with the [Frontend guide](/guides/frontend/connect-react) for the shared
contract and provider workflow; auth endpoints themselves use the native client below.

The framework-independent client and a sign-in action are:

```ts title="src/platform/auth-client.ts — browser code"
import { createAuthClient } from "better-auth/client";

// Browser code: use the public backend origin, never the server secret or service.
export const authClient = createAuthClient({
  baseURL: "http://127.0.0.1:3000",
  basePath: "/api/auth",
  fetchOptions: { credentials: "include" },
});

export async function signIn(email: string, password: string) {
  const { data, error } = await authClient.signIn.email({ email, password });
  if (error !== null) throw new Error(error.message ?? "Sign-in failed");
  return data;
}
```

Use your actual public backend origin instead of the loopback URL. `basePath` on this **client** must
match the filesystem auth mount; it is not the reserved server-service option. Do not import
`src/services/auth/service.ts`, database code, or server secrets into the browser. A public backend URL is
configuration, not a credential.

The action checks Better Auth's returned `error` before using `data`. Your UI should also handle
rejected requests, display an appropriate failure, and clear loading state after both success and
failure. Do not log passwords, cookies, or the complete auth response.

## Build the account flow

With email/password enabled, the native client maps these operations to the mounted API:

| User action             | Client method             | Native endpoint under `/api/auth` | Main input                  |
| ----------------------- | ------------------------- | --------------------------------- | --------------------------- |
| Create an account       | `authClient.signUp.email` | `POST /sign-up/email`             | `name`, `email`, `password` |
| Sign in                 | `authClient.signIn.email` | `POST /sign-in/email`             | `email`, `password`         |
| Read the native session | `authClient.getSession`   | `GET /get-session`                | No credentials in the body  |
| Sign out                | `authClient.signOut`      | `POST /sign-out`                  | No account password         |

The example enables automatic sign-in after a successful sign-up and does not require email
verification. If you change those policies, show the verification/sign-in step instead of assuming
every successful sign-up already has an active session. See [Customization](/docs/modules/auth/customization).

Let the browser store and send the session cookie. Do not copy the session token into local storage
or manually construct a `Cookie` header in browser JavaScript. Use Better Auth's sign-out endpoint to
invalidate the session; clearing a UI flag alone does not sign a user out on the server.

After sign-in, call your public `/session` operation or protected `/account/profile` route to verify
the application sees the same identity. `GET /session` is the example's small application response;
`GET /api/auth/get-session` is Better Auth's native session response. They are not interchangeable shapes.

## Keep cookies and origins aligned

The simplest local flow serves the page and API from `http://127.0.0.1:3000`. Use that hostname
consistently: switching between `localhost` and `127.0.0.1` changes the host to which cookies belong.

For a separate frontend origin, configure all three boundaries:

1. The client sends requests to the public backend URL with credentials included.
2. Better Auth's `trustedOrigins` allows the exact frontend origin for the auth flow.
3. The HTTP server or gateway supplies credentialed CORS for that origin, including preflight handling.

`trustedOrigins` is not CORS configuration, and CORS does not override cookie domain, `SameSite`, or
secure-cookie rules. Do not use a wildcard allowed origin with credentialed requests or disable
CSRF/origin checks to work around a configuration error. Test real browser requests over your intended
HTTPS domains; the in-process harness does not enforce browser cookie policy.

When using server-side rendering, forward only the current incoming request's relevant credentials
to the correct backend. A server-side fetch does not inherit the user's browser cookie jar. Likewise,
any auth response cookies must reach the browser through your framework's supported response handling.
Never share a mutable cookie/header object across users in a long-lived server client.

## Do not confuse bearer headers with authentication

After [installing the RELKIT client](/docs/modules/client), its transport can mutate request headers:

```ts title="src/platform/browser-client.ts — transport headers only"
import { createClient } from "@relkit/client";

export const bearerHeaders = new Headers();
export const client = createClient({ baseUrl: "http://127.0.0.1:3000", headers: bearerHeaders });

export function setBearerToken(token?: string): void {
  if (token === undefined) bearerHeaders.delete("authorization");
  else bearerHeaders.set("authorization", `Bearer ${token}`);
}
```

This example does **not** enable Better Auth bearer-token support. Adding an `Authorization` header
does not teach the server to validate it, and a Better Auth session is not automatically a JWT.
If you need non-cookie clients, configure and test the corresponding native auth capability on the
server and its client integration. Keep cookie authentication as the verified flow in this example.

For a framework-free browser demonstration, run `examples/auth-drizzle` using its README and open the
served page. The page uses the same native HTTP endpoints. For a reproducible command-line flow,
continue with [Your first auth flow](/guides/auth/email-password-sign-in).

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/better-auth` API](/docs/api/better-auth)
- [`@relkit/client` API](/docs/api/client)

- [`examples/auth-drizzle/src/platform/auth-client.ts`](https://github.com/rel-kit/relkit/blob/main/examples/auth-drizzle/src/platform/auth-client.ts)
- [`examples/auth-drizzle/public/index.html`](https://github.com/rel-kit/relkit/blob/main/examples/auth-drizzle/public/index.html)
