# Auth (https://form.dimah.dev/docs/auth)



Auth is not a library feature. Put it in `guard`. Use `on*` / `after*` hooks to stamp `respondentId` and fire emails or webhooks after a successful write.

<Flow
  label="Security & Execution Pipeline"
  steps="[
  {
    name: &#x22;1. Guard&#x22;,
    kind: &#x22;server&#x22;,
    note: &#x22;Authenticate & check permissions&#x22;,
  },
  {
    name: &#x22;2. Validate&#x22;,
    kind: &#x22;protocol&#x22;,
    note: &#x22;Validate answers vs snapshot&#x22;,
  },
  {
    name: &#x22;3. on* Hook&#x22;,
    kind: &#x22;server&#x22;,
    note: &#x22;Pre-write mutation (e.g. set respondentId)&#x22;,
  },
  { name: &#x22;4. Persist&#x22;, kind: &#x22;data&#x22;, note: &#x22;Atomic database write & CAS&#x22; },
  {
    name: &#x22;5. after* Hook&#x22;,
    kind: &#x22;server&#x22;,
    note: &#x22;Post-write side effects (email, webhook)&#x22;,
  },
]"
/>

***

## The `guard` Security Hook [#the-guard-security-hook]

The `guard` hook executes before any database operation or endpoint handler. If `guard` throws an `APIError`, the request is immediately rejected:

```ts title="lib/form.ts"
import {
  APIError,
  FORM_ERROR_CODES,
  dimahForm,
  memoryAdapter,
} from "@dimah-form/server";
import { forms } from "@/lib/forms";
import { getAuthSession } from "@/lib/auth";

export const form = dimahForm({
  database: memoryAdapter(),
  forms,
  guard: async ({ request, operation, formId, responseId, getResponse }) => {
    // 1. Authenticate user from session cookies or Bearer token
    const session = await getAuthSession(request);

    // 2. Restrict administrative endpoints to Admins
    const adminOperations = ["saveForm", "deleteForm", "listResponses"];
    if (adminOperations.includes(operation)) {
      if (!session || session.user.role !== "admin") {
        throw APIError.from("FORBIDDEN", FORM_ERROR_CODES.FORBIDDEN);
      }
    }

    // 3. Enforce Row-Level Respondent Ownership on response mutations
    if (responseId) {
      const record = await getResponse(responseId);

      if (record && record.respondentId) {
        if (!session || session.user.id !== record.respondentId) {
          throw APIError.from("FORBIDDEN", FORM_ERROR_CODES.FORBIDDEN);
        }
      }
    }
  },
});
```

### Context Passed to `guard` [#context-passed-to-guard]

| Argument          | Type                                                   | Description                                                                 |
| :---------------- | :----------------------------------------------------- | :-------------------------------------------------------------------------- |
| **`request`**     | `Request`                                              | Standard Fetch `Request` object with headers, cookies, and URL.             |
| **`operation`**   | `FormApiOperation \| string`                           | Name of the active operation (e.g., `"startResponse"`, `"submitResponse"`). |
| **`formId`**      | `string \| undefined`                                  | Form identifier if present in the query or body payload.                    |
| **`responseId`**  | `string \| undefined`                                  | Response identifier if present in the query or body payload.                |
| **`getResponse`** | `(id: string) => Promise<ResponseRecord \| undefined>` | Helper to fetch the current response record from the store.                 |

***

## Sending Client Credentials & Headers [#sending-client-credentials--headers]

Configure `createFormClient` to automatically include auth tokens or session cookies with every request:

```ts title="lib/form-client.ts"
"use client";

import { createFormClient } from "@dimah-form/react";
import type { Form } from "@/lib/form";
import { getClientToken } from "@/lib/auth-client";

export const formClient = createFormClient<Form>({
  basePath: "/api/form",
  credentials: "include", // Include browser session cookies
  headers: async () => {
    const token = await getClientToken();
    return token ? { Authorization: `Bearer ${token}` } : {};
  },
});
```

***

## Pre-Write & Post-Write Lifecycle Hooks [#pre-write--post-write-lifecycle-hooks]

Lifecycle hooks allow you to mutate records before persistence or run side-effects after persistence:

```ts title="lib/form.ts"
import { dimahForm, memoryAdapter } from "@dimah-form/server";
import { sendEmailNotification } from "@/lib/notifications";
import { getAuthSession } from "@/lib/auth";

export const form = dimahForm({
  database: memoryAdapter(),
  hooks: {
    // 1. Pre-write: Runs before creating a new response in DB
    onStart: async ({ request, response }) => {
      const session = await getAuthSession(request);
      if (session) {
        response.respondentId = session.user.id;
      }
    },

    // 2. Pre-write: Runs before saving a draft
    onDraft: async ({ request, response, patch }) => {
      console.log(`User updating draft for response ${response.id}`);
    },

    // 3. Post-write: Runs after successful submit transaction
    afterSubmit: async ({ response }) => {
      await sendEmailNotification({
        to: "team@company.com",
        subject: `New response submitted for form ${response.formId}`,
        responseId: response.id,
      });
    },

    // 4. Post-write: Runs after response is deleted
    afterDelete: async ({ responseId }) => {
      console.log(`Response ${responseId} was deleted.`);
    },
  },
});
```

### Hook Execution Rules [#hook-execution-rules]

* **Pre-write (`on*`)**: Synchronously or asynchronously mutates the `response` record before the database write commits. If an error is thrown, the write is aborted.
* **Post-write (`after*`)**: Executes after the database write has succeeded. Safe for external network calls, transactional emails, or message queues.

***

## Next Steps [#next-steps]

<Cards>
  <Card title="Errors" href="/docs/errors" description="Review UNAUTHORIZED, FORBIDDEN, and standard error shapes." />

  <Card title="Plugins" href="/docs/plugins" description="Learn how plugins can contribute custom endpoints and guard checks." />

  <Card title="React Client" href="/docs/react" description="Pass credentials and handle authentication errors in useFormResponse." />
</Cards>
