# §26 — The API with accounts

You are holding the password now. That is one hash, one reset flow, one rate limit on the login route, and one decision you cannot take back quietly.

`signup` and `login` are route handlers, not a framework — they take your table and your field names and hand you the user. What you do with the user is yours: the token below is a JWT, but a database-backed revocable session from `@atlas/security` is the better default the moment you need to kick someone out.

`token.sign` and `token.verify` are async. Forgetting the `await` yields a Promise that stringifies to `[object Promise]` and a token that verifies as garbage.

## What you are carrying

- `@atlas/server` — Routes and the pipe pipeline.
- `@atlas/db` — The users table.
- `@atlas/auth` — Hashing, JWTs, signup and login flows.
- `@atlas/security` — Rate limits, audit log, revocable sessions, TOTP.
- `@atlas/email` — The reset mail you will need by week two.

## Start it

```bash
atlas init -n myapi --template api
```

## What it looks like

`src/auth.ts`

```ts
import { token, signup, login, requireAuth } from "@atlas/auth"
import { post, pipeline, parseJson, json } from "@atlas/server"

export const routes = [
  post("/auth/signup", pipeline(parseJson)(signup({
    db, table: "users", fields: ["email", "name", "password"],
    onSuccess: (c, user) => json(c, 201, { id: user.id, email: user.email }),
  }))),

  post("/auth/login", pipeline(parseJson)(login({
    db, table: "users", identity: "email", password: "password",
    // token.sign is async — the missing await is the bug you will not see.
    onSuccess: async (c, user) =>
      json(c, 200, { token: await token.sign({ id: user.id }, secret) }),
  }))),
]

export const guard = requireAuth({ secret })
```

## Where now

- Put it online — turn to §100 (Appendix A)
- Hand it to an agent — turn to §102 (Appendix C)
- Walk it again from the start — turn to §1
