---
title: Authentication
description: Three kinds of principal, three mechanisms, and the one that cannot step up.
---

Four principal types, three authentication mechanisms.

| Principal | Authenticates with | Reaches |
| --- | --- | --- |
| `USER` | Password, MFA, or SSO → session | Control plane |
| `API_KEY` | A bearer key | Control plane |
| `AGENT` | A signed Ed25519 assertion → short-lived token | **Gateway only** |
| `SYSTEM` | Internal | Background jobs |

`principal.type` is a policy field, so a rule can apply to automation but not to
people, or the reverse.

## Human sessions

```http
POST /v1/auth/signup     # creates an organization and its first owner, one transaction
POST /v1/auth/login
POST /v1/auth/mfa/verify
POST /v1/auth/step-up
GET  /v1/auth/session
POST /v1/auth/logout
```

Login returns a session token, or an MFA challenge to complete first.

```bash
curl -s $CP/v1/auth/session -H "Authorization: Bearer $SESSION"
```

Returns the current session, its scope, and the **effective permissions** — the
authoritative answer to "what can I do", and what the console uses to decide
what to render.

| Setting | Default |
| --- | --- |
| `SESSION_TTL_SECONDS` | 43200 (12 hours) |
| `SESSION_IDLE_TIMEOUT_SECONDS` | 3600 |
| `STEP_UP_TTL_SECONDS` | 300 |
| `MAX_LOGIN_ATTEMPTS` | 10 |
| `LOGIN_LOCKOUT_SECONDS` | 900 |

Signup creating the organization and its first owner in one transaction means
there is no window in which an organization exists with nobody able to
administer it.

### Step-up

```http
POST /v1/auth/step-up
{ "password": "…" }
```

Thirteen permissions require a fresh re-authentication at the moment of use,
regardless of session age. Without a current step-up they return
`403 STEP_UP_REQUIRED`.

A twelve-hour session is a reasonable credential for reading dashboards and not
for revoking a production agent. [→ Step-up](/secure/step-up)

### CLI

```bash
veefore login
```

Sign-in happens in the browser, never in the terminal. The CLI opens the
console, you sign in there (password, MFA or SSO, exactly as for the console)
and approve the terminal. The CLI receives a session token over a loopback
redirect protected with PKCE (`S256`), so the terminal never sees your password.

| Situation | What happens |
| --- | --- |
| A browser on the same machine | Opens it and waits for your approval |
| Over SSH, no display, or `--device` | Prints a link and a short code to enter in any browser |
| `--password` | Asks for email and password in the terminal, without echo |

Only the session token is stored, in `~/.veefore/credentials.json` at mode
`0600`. Revoke it from your profile in the console like any other session.

`veefore logout` ends the session on the server as well as removing the stored
token. Signing out of the console asks whether to **also sign out of the Veefore
CLI on your devices**, ticked by default; that ends every terminal session you
approved through browser sign-in. A terminal cannot be told the moment that
happens, so the next command there reports that you were signed out, removes
the stored token and asks you to sign in again. Sessions signed in with
`VEEFORE_EMAIL` and `VEEFORE_PASSWORD`, as in CI, are not affected.
The CLI connects to Veefore by itself. For a self-hosted Veefore, add
`--control-plane https://veefore.your-company.com` the first time; later
commands reuse it.

For CI, where nobody can open a browser, prefer an [API key](#api-keys). If a
pipeline must sign in as a user, set the credentials in the environment and the
CLI uses them without prompting:

```bash
VEEFORE_EMAIL=ci@example.com VEEFORE_PASSWORD=… veefore login
```

## API keys

```http
Authorization: Bearer aos_sk_…
```

For a service or a pipeline. Scoped to a role, optionally to a workspace, with
an expiry up to 730 days.

```bash
curl -X POST $CP/v1/api-keys \
  -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{ "name": "ci-policy-gate", "role_id": "role_01JAV…", "workspace_id": "wsp_01JAV…", "expires_in_days": 90 }'
```

:::danger Shown exactly once, as `token`
Only a hash is stored and there is no endpoint that returns it again. The field
is `token`; `api_key` is the metadata record.
:::

:::security An API key cannot step up
Step-up requires a human re-authenticating, and a key has no human. A key
holding `policy:activate` **still cannot activate a policy**.

Which means CI cannot activate a policy, revoke an agent, rotate a credential or
execute an emergency control — regardless of its role. Deliberate: activation
moves the authorization boundary for every agent in scope. It is a decision, not
a build step.
:::

Pick the smallest role. Never `OWNER` — it holds `organization:delete`, and a CI
secret is readable by every build step. [→ API keys](/build/api-keys)

## Agent authentication

Agents authenticate at the **gateway**, with a signed assertion. No passwords,
no API keys, no human credentials.

```http
POST /gateway/v1/token
{
  "agent_id": "agt_01JAV…",
  "assertion": "eyJ0eXAiOiJhZ2VudG9zLWFzc2VydGlvbit…",
  "sdk": { "name": "@veefore/sdk", "version": "1.0.0", "runtime": "node20" }
}
```

```json
{
  "data": {
    "token": "aos_at_…",
    "token_type": "Bearer",
    "expires_in": 900,
    "expires_at": "2026-10-03T12:53:04.000Z",
    "agent_id": "agt_01JAV…",
    "environment_id": "env_01JAV…",
    "organization_id": "org_01JAV…"
  }
}
```

Or `POST /gateway/v1/handshake`, which also returns the authorized tool list,
the capabilities, the enforcement mode and the passport version.

### What is checked

| Check | Refusal |
| --- | --- |
| Signature against the stored public key | `AGENT_AUTHENTICATION_FAILED` |
| `aud` matches the published audience, as a string | `AGENT_AUTHENTICATION_FAILED` |
| Timestamp inside the window | `TIMESTAMP_OUT_OF_WINDOW` |
| Nonce not seen before | `NONCE_REPLAYED` |
| Key generation current | `IDENTITY_GENERATION_STALE` |
| Identity not revoked | `AGENT_IDENTITY_REVOKED` |

:::warning The audience is compared as a string
`http://127.0.0.1:4010` and `http://localhost:4010` are the same host and
different values. This is the most common onboarding failure and it presents as
a bare `401`.

```bash
curl -s https://gateway.veefore.example.com/.well-known/veefore-platform-key
```

Use the `audience` from that response verbatim.
:::

### No refresh token

| | |
| --- | --- |
| Token lifetime | `AGENT_TOKEN_TTL_SECONDS`, 900 |
| Refresh | None. Sign a fresh assertion |
| On key rotation | Every session dies; old assertions get `IDENTITY_GENERATION_STALE` |
| On revocation | Every session dies; `AGENT_IDENTITY_REVOKED` |

:::security There is no refresh token on purpose
A refresh token is a long-lived bearer credential, and the agent already holds
something strictly better — the private key. Re-signing costs a signature.

Treat `401` as "mint a new token and retry once". A second `401` is a real
identity problem; stop.
:::

Full wire format: [Sign your own assertion](/build/sign-your-own-assertion).

## Comparison

| | Session | API key | Agent token |
| --- | --- | --- | --- |
| Principal | `USER` | `API_KEY` | `AGENT` |
| Obtained by | Password / MFA / SSO | Created once | Signed assertion |
| Lifetime | 12h, 1h idle | Up to 730 days | 15 minutes |
| Reaches | Control plane | Control plane | **Gateway only** |
| Can step up | **yes** | **no** | n/a |
| Refreshable | re-login | n/a | re-sign |
| Revocable | logout | revoke | revoke the identity |

## The published values

```bash
curl -s https://gateway.veefore.example.com/.well-known/veefore-platform-key
```

The platform **public** key, its id, and the gateway audience. No
authentication, cached for five minutes.

:::security Publishing the audience is safe
It is the value you must sign *for*. Knowing it grants nothing without the
agent's private key, and every agent that can reach the gateway has to know it
to talk at all.

Not publishing it would mean clients guessing, and a wrong guess produces a bare
`401` with no explanation — which was a real onboarding failure before this
endpoint carried it.
:::

## Related

:::cards

:::card Conventions | /reference/conventions
Envelopes, scope headers, correlation.
:::

:::card Agent identity | /concepts/identity
The key pair and its lifecycle.
:::

:::card Step-up | /secure/step-up
The thirteen gated permissions.
:::

:::card Authentication API | /reference/api/authentication
Every endpoint. Generated.
:::

:::
