---
title: Authentication overview
description: Understand authentication, sessions, and authorization, and decide where each responsibility belongs in a RELKIT application.
pagination: false
sidebar:
  icon: shield-check
---

Authentication tells your application who is making a request. A session lets that caller make subsequent
requests without entering credentials again. Authorization decides what that identity may do: read an
order, join an organization, or change another user's settings. Signing in does not grant those permissions.

> **Start here**
>
> Start with [Add email and password sign-in](/guides/auth/email-password-sign-in) to register a
> user and verify a protected endpoint. Use the [Better Auth reference](/docs/api/better-auth) for
> the integration's exact options and types.

Keep these concepts separate from the authentication library you choose. An auth service owns identity
and session integration; business domains own permission checks and product behavior. Your routes and
clients connect those responsibilities without becoming a second authentication system.

## Pre-setup

For a new app, first complete [PostgreSQL in local Docker](/docs/modules/database/local-docker).
Authentication shares that database. If your app already uses another supported dialect,
keep it and pass the matching `--database-dialect` below.

1. **Scaffold the authentication service**

    Run once in the backend root when it has no auth service:

    ```sh title="Terminal — application root"
    bun run relkit add auth --adapter better-auth --database-dialect postgresql
    ```

    The command installs the selected packages, adds auth tables to the existing Drizzle schema,
    creates the auth service and `/api/auth` mount, and merges environment declarations. It does
    not generate a secret, apply migrations, or protect your business endpoints.

2. **Configure environment values**

    Keep these fields in `src/platform/env.ts` and its existing import in `relkit.config.ts`.
    This minimal inline configuration shows the required contract; merge rather than replacing
    your application's other fields and settings:

    ```ts title="relkit.config.ts — equivalent inline environment"
    /** Auth and Drizzle share one database; credentials remain in the backend environment. */
    import { defineApp, defineEnv, env } from "@relkit/app/config";

    export default defineApp({
      env: defineEnv({
        DATABASE_URL: env.secret(),
        BETTER_AUTH_URL: env.url(),
        BETTER_AUTH_SECRET: env.secret(),
      }),
    });
    ```

    Retain `DATABASE_URL` from the Docker setup. Generate a secret once:

    ```sh title="Terminal — application root"
    openssl rand -base64 32
    ```

    Add `BETTER_AUTH_URL=http://127.0.0.1:3000` and `BETTER_AUTH_SECRET=<generated value>` to your
    untracked `.env.local`, using your actual backend origin and generated secret. Keep the same
    secret across restarts. Better Auth reads it from the backend process environment; `defineEnv`
    validates it but does not inject parsed values into native auth options. Never expose it to the frontend.

3. **Apply auth tables and verify**

    ```sh title="Terminal — application root"
    bun run relkit env check
    bun --bun drizzle-kit generate --name=add-auth
    ```

    Review the generated changes, preserving existing tables and applied migrations. Then run:

    ```sh title="Terminal — application root"
    bun run relkit db migrate
    bun run check
    bun run typecheck
    bun run dev
    ```

    Continue with [Better Auth options](/docs/modules/auth/better-auth) and
    [protecting application routes](/docs/modules/auth/protect-routes). Verify sign-up and cookies
    using the [sign-in guide](/guides/auth/email-password-sign-in); its standalone SQLite sample is
    an alternative, so keep your PostgreSQL service and migrations when adapting its requests.

    Update application HTTP fixtures to use `@relkit/testing/better-auth`, including fixtures for
    public routes. Supply explicit test environment values and an isolated, migrated database;
    the harness does not inherit `.env.local`. See [testing authentication](/docs/modules/auth/testing).

## Organize authentication by responsibility

| What you need to add                          | Where it belongs                                                      | Continue with                                                                                               |
| --------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Sign-in methods, session policy, auth plugins | `src/services/auth/service.ts`, using the chosen integration          | [Better Auth service](/docs/modules/auth/better-auth)                                                       |
| Auth tables, connection, schema changes       | The existing database domain and migration tooling                    | [Database integration](/docs/modules/database/better-auth)                                                  |
| Public authentication endpoints               | A filesystem route under `src/routes/`                                | [Protect routes](/docs/modules/auth/protect-routes)                                                         |
| The current caller in a business operation    | The owning domain's function                                          | [Read sessions](/docs/modules/auth/sessions)                                                                |
| Ownership, roles, organization access         | The business operation that reads or changes the resource             | [Authorization boundaries](/docs/modules/auth/protect-routes#enforce-permissions-in-the-business-operation) |
| Sign-up forms and sign-in state               | Your frontend and auth client's public configuration                  | [Clients](/docs/modules/auth/clients)                                                                       |
| Background work on behalf of a user           | The owning job/function, with an explicit trusted identity and policy | [Sessions outside HTTP](/docs/modules/auth/sessions#work-without-an-http-request)                           |

This separation keeps identity plumbing out of every domain. Changing an auth provider's settings does
not require moving Orders functions; changing an Orders permission rule does not require rewriting the
login route. See [Service organization](/docs/modules/service/organization) for the wider application layout.

## Understand the request boundary

```text
Browser → auth endpoint → authentication service → persisted identity/session
Browser → application endpoint → session check → business function → permission check → data
```

The auth endpoint owns credential validation and session cookies. Application endpoints either allow
anonymous callers or require a session. A business function uses the verified identity to enforce its
own rules. Never treat an arbitrary input field, header, or client-side “logged in” flag as verified identity.

Public service functions can also be called outside HTTP. Protecting a route does not secure every job,
tool, or direct invocation of the same function. Put the permission decision at the operation boundary
and explicitly define which trusted non-HTTP callers may invoke it.

## Choose the current integration

[Better Auth](/docs/modules/auth/better-auth) is the supported auth service integration today. It uses the
application's [Drizzle service](/docs/modules/database/drizzle), mounts a native HTTP handler, and supplies
`context.auth.getSession()` to application functions. Other auth libraries do not gain this wiring just
because their file is named `service.ts`; these guides do not promise a universal provider API.

The getting-started concepts apply generally. Better Auth options, cookie behavior, plugins, and table
requirements belong to the integration guides. For a complete local flow, follow
[Your first auth flow](/guides/auth/email-password-sign-in).

After changing service or route registration, run from your app root:

```sh
bun run check
bun run typecheck
```

These check discovery and types, not real credentials, schema migrations, or browser cookie behavior.

## Find the guide for your task

**Configure the integration and secrets:**

[Better Auth service](/docs/modules/auth/better-auth).

**Read a nullable, request-bound identity:**

[Sessions](/docs/modules/auth/sessions).

**Require authentication and enforce permissions:**

[Protect routes](/docs/modules/auth/protect-routes).

**Build sign-up, sign-in, and sign-out flows:**

[Clients](/docs/modules/auth/clients).

**Add providers, plugins, fields, and production policy:**

[Customize authentication](/docs/modules/auth/customization).

**Verify sessions and failure cases:**

[Testing](/docs/modules/auth/testing).

**Add authentication to an existing app:**

[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/services` API](/docs/api/services)

- [`examples/auth-drizzle/src/services/auth/service.ts`](https://github.com/rel-kit/relkit/blob/main/examples/auth-drizzle/src/services/auth/service.ts)
