AkurAI Build
Menu

Public repository / master

AkurAI-Framework / site/content/docs/auth.md

Latest change 0bb96d8dc2b9ebd83dff1e8f321a26b5367af139 - feat(cli): cookie-based session auth + login flow (Batch 4 F3) by Ólafur Búi Ólafsson

# Auth & sessions

AkurAI/Framework ships a reference authentication feature: cookie-based sessions
on the embedded B+tree, a tiny users table, a plain-HTML login form, a protected
page, and logout. It is built the same way as the [forms](/docs/forms) demo —
validate, store, redirect — and stores nothing in memory, so logins survive a
restart.

> **This is a framework demo, not a hardened auth system.** The PRNG and the
> password hash are deliberately simple and `std`-only. Read the
> [security caveats](#security-caveats) before shipping anything real on top of
> it.

## The session model

A session is a row in `data/sessions.db`, keyed by an **opaque session id** and
holding the username it belongs to plus a created-at timestamp (unix seconds).
The id is the only thing the browser ever sees — it is the cookie value.

Users live in a separate `data/users.db`, keyed by username, storing a salted
password hash (never the plaintext). A demo user is seeded on first boot.

| Store | File | Key | Value |
| --- | --- | --- | --- |
| Sessions | `data/sessions.db` | opaque session id | `{ username, created }` |
| Users | `data/users.db` | username | `{ salt, hash }` |

Both are `Arc<Mutex<BTree>>` handles — the same durable, single-writer storage
engine the guestbook uses. Every write commits, so a session is durable the
moment login returns.

## The login / logout / protected-route flow

```
GET  /login    → render the login form
POST /login    → validate credentials
                 ├─ ok   → create session, Set-Cookie, 303 → /account
                 └─ fail → re-render the form with an error (username kept)
GET  /account  → if a valid session cookie is present, show the user
                 otherwise 303 → /login
POST /logout   → delete the session, expire the cookie, 303 → /login
```

The flow is pure progressive enhancement: ordinary `<form method="post">`
elements, Post/Redirect/Get so a refresh never resubmits, and no JavaScript
required. An already-signed-in visitor hitting `/login` is bounced straight to
`/account`.

### The session middleware helper

`auth::State::current_user(req)` is the middleware/helper that gates protected
routes. It reads the `Cookie` request header, parses out the `session` value,
looks it up in the session store, and returns the username (or `None`). The
`/account` handler is just:

```rust
let Some(username) = state.current_user(req) else {
    return Reply::Response(redirect("/login"));
};
```

## The cookie

On success the server sends:

```
Set-Cookie: session=<id>; HttpOnly; Path=/; SameSite=Lax
```

| Attribute | Why |
| --- | --- |
| `HttpOnly` | JavaScript cannot read the cookie, blunting XSS session theft. |
| `Path=/` | The session applies to the whole site. |
| `SameSite=Lax` | The cookie rides top-level navigations but not cross-site sub-requests, a baseline CSRF mitigation. |

Logout sends the same cookie with an empty value and `Max-Age=0`, so the browser
drops it immediately, and deletes the row from `data/sessions.db`.

## The demo user

| Username | Password |
| --- | --- |
| `demo` | `akurai` |

Seeded on first boot via an idempotent `ensure_user` — re-running the server
never resets an existing user's password. Visit [`/login`](/login) and sign in
to see the protected [`/account`](/account) page.

## Security caveats

This feature exists to show the *shape* of session auth on the framework's own
storage engine, with zero external crates. It is **not** production-grade:

- **Demo-grade PRNG.** Session ids are 128-bit values derived from `std` only —
  `SystemTime` nanos mixed with a per-process atomic counter and the pid, run
  through a SplitMix64 diffuser. This is CSPRNG-*lite*, not a cryptographically
  secure RNG. For real deployments, source session ids from OS randomness.
- **Demo-grade password hash.** Passwords are stored as a salted, stretched
  SHA-1 (reusing the WebSocket handshake's SHA-1). It is salted and iterated,
  but SHA-1 is fast and not memory-hard — this is **not bcrypt or argon2** and
  must not be described as such.
- **TLS terminates at the edge.** The cookie is intentionally *not* marked
  `Secure`, because in the reference deployment HTTPS is terminated by nginx in
  front of the app. The session cookie's confidentiality in transit relies on
  that edge TLS layer; if you expose the app directly, add `Secure` and serve
  over HTTPS.
- **No expiry/rotation.** Sessions are kept until logout. A real system would
  add idle/absolute timeouts and id rotation on privilege change.