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

# One-time tokens

> Single-use tokens for email verification, password resets, invitations, and custom flows.

One-time tokens are short-lived, single-use strings for flows like email verification, password resets, and invitations. The raw token is handed to the caller once and never stored, only a SHA-256 hash lives in the database. On first use (or expiry), the token is gone.

## Setup

The module is part of KavachOS core. No extra plugin needed.

```typescript title="lib/kavach.ts" theme={"system"}
import { createKavach } from 'kavachos';

const kavach = await createKavach({
  database: { provider: 'postgres', url: process.env.DATABASE_URL! },
  secret: process.env.KAVACH_SECRET!,
  baseUrl: 'https://auth.example.com',
});

// Access the module
const tokens = kavach.oneTimeTokens;
```

## Token purposes

Each token has a `purpose` that scopes its validity. Validation fails if the purpose at creation does not match the purpose at consumption.

| Purpose          | Use                                   |
| ---------------- | ------------------------------------- |
| `email-verify`   | Confirm a new email address           |
| `password-reset` | Authenticate a password-reset request |
| `invitation`     | Invite a user to an org or workspace  |
| `custom`         | Any application-specific flow         |

## Creating a token

`createToken` returns the raw token exactly once. Put it in a link or hand it to your mailer, there is no way to recover it from the database later.

<Tabs>
  <Tab title="password reset">
    ```typescript theme={"system"}
    const result = await tokens.createToken({
      purpose: 'password-reset',
      identifier: 'alice@example.com', // email, user ID, or any scoping key
      ttlSeconds: 1800,                // 30 minutes
    });

    if (result.success) {
      const { token, expiresAt } = result.data;
      await mailer.send({
        to: 'alice@example.com',
        subject: 'Reset your password',
        html: `<a href="https://app.example.com/reset?token=${token}">Reset password</a>`,
      });
    }
    ```
  </Tab>

  <Tab title="email verify">
    ```typescript theme={"system"}
    const result = await tokens.createToken({
      purpose: 'email-verify',
      identifier: user.id,
      ttlSeconds: 86400, // 24 hours
    });

    if (result.success) {
      await sendVerificationEmail(user.email, result.data.token);
    }
    ```
  </Tab>

  <Tab title="invitation">
    ```typescript theme={"system"}
    const result = await tokens.createToken({
      purpose: 'invitation',
      identifier: 'invited@example.com',
      ttlSeconds: 604800, // 7 days
      metadata: { orgId: 'org_abc123', role: 'member' }, // [!code highlight]
    });
    ```
  </Tab>
</Tabs>

The default TTL is **3600 seconds** (1 hour). Override it per call with `ttlSeconds`, or set `defaultTtlSeconds` on the module config to change the default for all tokens.

## Validating a token

Call `validateToken` when the user lands on your reset or verification page. On success, the token is consumed immediately, a second call with the same token always fails.

```typescript theme={"system"}
const result = await tokens.validateToken(
  incomingToken, // from the URL query param
  'password-reset',
);

if (!result.success) {
  // result.error.code is one of:
  // TOKEN_NOT_FOUND | TOKEN_ALREADY_USED | TOKEN_EXPIRED | TOKEN_PURPOSE_MISMATCH
  return { error: result.error.message };
}

const { identifier, metadata } = result.data;
// identifier === 'alice@example.com'
// Proceed with the reset
```

<Warning>
  `validateToken` marks the token as used before it returns. Even if your handler crashes after this call, the token cannot be reused. Handle the downstream action (password update, email confirmation) in the same request.
</Warning>

## Revoking tokens

Revoke all active tokens for an identifier when a user takes an action that makes them obsolete, for example, invalidating outstanding reset links when a user changes their password through a different flow.

```typescript theme={"system"}
// Revoke all active password-reset tokens for this user
const result = await tokens.revokeTokens('alice@example.com', 'password-reset');

if (result.success) {
  console.log(`Revoked ${result.data.count} token(s)`);
}

// Revoke everything for this identifier (all purposes)
await tokens.revokeTokens(user.id);
```

Revocation is a soft operation, tokens are marked as used, not deleted. Expired tokens are excluded from the count.

## Attaching metadata

Pass a `metadata` object to store arbitrary data alongside the token. It is returned on successful validation.

```typescript theme={"system"}
const result = await tokens.createToken({
  purpose: 'invitation',
  identifier: 'bob@example.com',
  metadata: { orgId: 'org_xyz', role: 'admin', invitedBy: 'alice' },
});

// On validation:
const validation = await tokens.validateToken(token, 'invitation');
if (validation.success) {
  const { orgId, role } = validation.data.metadata as { orgId: string; role: string };
  await addUserToOrg(user.id, orgId, role);
}
```

## Error codes

| Code                     | Cause                                                    |
| ------------------------ | -------------------------------------------------------- |
| `TOKEN_NOT_FOUND`        | Token does not exist or was already deleted              |
| `TOKEN_ALREADY_USED`     | Token was consumed by a previous call                    |
| `TOKEN_EXPIRED`          | Token's `expiresAt` is in the past                       |
| `TOKEN_PURPOSE_MISMATCH` | Purpose at validation does not match purpose at creation |
| `INVALID_INPUT`          | Empty token string or unknown purpose value              |
| `CREATE_TOKEN_FAILED`    | Database write failed                                    |
| `REVOKE_TOKENS_FAILED`   | Database update failed during revocation                 |

## Security notes

**Tokens are hashed at rest.** Only a SHA-256 hash is stored. A database dump does not expose usable tokens.

**Single-use enforcement is atomic.** The mark-as-used update runs before the result is returned, with a conditional `WHERE used = false`. Concurrent requests for the same token will fail at the database level.

**Purpose binding prevents cross-flow reuse.** A password-reset token cannot be submitted to an email-verify endpoint.
