---
title: "Authentication modes"
summary: "Local codes versus an identity provider, who owns roles in each, and what happens on every login."
description: "Why Pitot has two sign-in modes, what each one owns, and where the boundary between them sits."
status: preview
read_when:
  - "You are choosing between local sign-in and OIDC."
  - "You expect a provider group change to take effect and want to know when it does."
---
Pitot supports local sign-in and OIDC, selected by `PITOT_AUTH_MODE`.
Both use the same [roles and capabilities](/docs/reference/roles), browser sessions
and account suspension rules.

## Local

Active users sign in with an emailed code when SMTP is configured. Without
SMTP, an operator with access to the control database mints a single-use link
with `pitot login-link <email>`. Administrators manage accounts and roles
through the HTTP API. There is no user-management browser page. See
[account profiles and suspension](#account-profiles-and-suspension)
for account APIs and [local recovery](#recover-local-sign-in) for invitation delivery
and recovery.

`PITOT_SELF_SIGNUP` allows verified email addresses to create viewer accounts.
It defaults to `false` and requires complete SMTP configuration. Self-signup is
available only after the first administrator completes setup. Viewers can read
all telemetry, manage their own dashboards and run the built-in agent.
The built-in agent sends requests to your configured model provider and can
incur charges. Enabling self-signup grants that access to anyone who can verify
an email address.

## OIDC

An identity provider handles sign-in. Group and domain allowlists are evaluated
on every login, including for linked identities. Removing a user's eligibility
blocks their next login; it does not revoke a browser session already issued.
Suspending the account in Pitot blocks access and revokes its sessions.

Pitot starts even when the identity provider is unreachable, so ingest, MCP
and existing sessions keep working. Discovery retries in the background with
backoff capped at 1 minute. Until it succeeds, the OIDC sign-in routes return
503 and the `oidc` check in `/readyz` reads `degraded`; no restart is needed.
Invalid configuration, including a provider that answers for a different
issuer at startup, still fails startup.

If neither `PITOT_OIDC_ALLOWED_GROUPS` nor `PITOT_OIDC_ALLOWED_DOMAINS` is
configured, no eligibility allow policy is enforced. Membership is then managed
locally, unless a role mapping is set; see [Who owns accounts](#who-owns-accounts).

### Account provisioning

`PITOT_OIDC_AUTO_PROVISION` creates eligible accounts on first sign-in after
administrator setup. It requires an allowed group or domain. The role comes
from the configured group mapping, falling back to `PITOT_OIDC_DEFAULT_ROLE`
(`viewer`).

`PITOT_OIDC_EMAIL_VERIFICATION=required` requires a verified email. The `issuer`
policy trusts the configured issuer and requires an allowlist. See
[Microsoft Entra ID group overage](#microsoft-entra-id-group-overage) if group
claims are omitted.

### Role ownership

If `PITOT_OIDC_ADMIN_GROUPS` or `PITOT_OIDC_OPERATOR_GROUPS` is configured, the
provider reapplies the mapped role on every login. Users outside mapped groups
receive `PITOT_OIDC_DEFAULT_ROLE`. A role change revokes browser sessions and
writes an audit event in the same transaction.

Without a role-group mapping, administrators manage roles inside Pitot and
OIDC sign-in preserves them. Reconciliation cannot demote the last active
administrator; promote a second administrator before applying that mapping.

### Who owns accounts

Role mapping moves account ownership to the identity provider.
`GET /api/auth/status` reports the mode as `account_ownership`, and Settings
explains it to signed-in users.

| `account_ownership` | Configuration | Browser user routes refuse |
|---|---|---|
| `local` | No role mapping, with or without a SCIM token | Nothing |
| `identity_provider_roles` | Role mapping and `PITOT_OIDC_AUTO_PROVISION`, no SCIM token | Creating a user and changing a role |
| `identity_provider_roles_no_provisioning` | Role mapping, no `PITOT_OIDC_AUTO_PROVISION`, no SCIM token | Creating a user and changing a role |
| `identity_provider_directory` | Role mapping and a SCIM token | Creating and deleting a user; changing a role, status or email |

To add users under a role mapping, set `PITOT_OIDC_AUTO_PROVISION` with an
allowed group or domain, or configure a SCIM token. With neither, nobody new can
get an account: `POST /api/users` is refused and a first login is denied. Pitot
still starts, logs a warning, and Settings says so.

A refused request returns `403` with the code `managed_by_identity_provider`
and changes nothing. On `PATCH /api/users/{id}`, malformed JSON returns `400`
because the body must be decoded to see whether `email` is present. SCIM, login-time role reconciliation and provisioning,
display-name edits, `POST /api/users/{id}/access/revoke`, first-administrator
setup and `pitot login-link --break-glass` keep working in every mode.
Without a SCIM token, administrators can still suspend, reactivate, delete and
change the email of a user.

### Microsoft Entra ID group overage

Entra can omit the `groups` claim when an account has too many memberships.
If the token signals group overage and any group-based policy is configured,
Pitot denies login. It does not fetch group membership from Graph.
Configure the application registration to emit only groups assigned to the
application. Domain-only allow policies are unaffected.

## Account profiles and suspension

Administrators create active accounts with `POST /api/users`, supplying `email`,
`display_name` and `role`. Edit profiles with `PATCH /api/users/{id}`; change
roles with `PATCH /api/users/{id}/role` and `{"role":"viewer"}`. Suspend with
`PATCH /api/users/{id}/status` and `{"status":"suspended"}`; restore with
`{"status":"active"}`. Unknown fields and invalid statuses return `400`.
Email, role and status changes revoke browser sessions. Suspension blocks local
and OIDC sign-in and revokes the user's OAuth tokens. Reactivation revokes any
that remain, so a reactivated user signs in again. The last active administrator cannot be demoted, suspended or deleted.
With an OIDC role mapping some of these routes are refused; see
[Who owns accounts](#who-owns-accounts).

`GET /api/users` returns a `users` array. `DELETE /api/users/{id}` removes an
account and cascades to its sessions, OAuth grants and owned dashboards. Use
suspension when the account and its data should remain.

`POST /api/users/{id}/access/revoke` revokes all browser sessions and OAuth
tokens for that user. It preserves the account. A user who remains eligible
can sign in again.

### Call account APIs from a script

These routes accept only a browser session with the admin role. Pitot has no
API key for user administration. Ingest tokens, metrics tokens and MCP OAuth
tokens cannot substitute for that session.

Sign in as an administrator. In browser developer tools, find an authenticated
request and copy its session cookie into a temporary Netscape-format cookie
jar supported by curl. Keep it private and outside source control. A script
can use that jar while the session remains valid:

```sh
curl --fail-with-body --cookie "$COOKIE_JAR" \
  https://pitot.example.com/api/users

curl --fail-with-body --cookie "$COOKIE_JAR" \
  -H 'Pitot-Request: 1' -H 'Content-Type: application/json' \
  -X POST https://pitot.example.com/api/users \
  --data '{"email":"user@example.com","display_name":"New user","role":"viewer"}'
```

Replace the origin and set `COOKIE_JAR` to that private jar. Mutating requests
need `Pitot-Request: 1` or a matching Origin/Referer; cross-site browser requests
are rejected. Session expiry or revocation requires signing in again. Delete
the temporary jar when finished and sign out to revoke that session.

## SCIM provisioning

Set `PITOT_SCIM_TOKEN` (or `PITOT_SCIM_TOKEN_FILE`) so an identity provider or
automation can create, update and deactivate accounts. It requires
`PITOT_AUTH_MODE=oidc` and at least 32 characters. Without a token the routes
do not exist.

With a token, Pitot prints no setup banner and `/api/auth/setup` returns `410`;
the first administrator comes from SCIM.

Pitot implements a small subset of SCIM 2.0 under `/scim/v2`. Send
`Authorization: Bearer <token>`. Responses and errors use
`application/scim+json` and the RFC 7644 formats.

- `POST /scim/v2/Users` creates an account from `externalId`, `userName` (the
  email address) and one `roles` value of `admin`, `operator` or `viewer`. The
  account and its identity at the configured issuer are written together, so the
  first OIDC login needs no match by email. Repeating the request returns the
  existing account with `200` and changes nothing.
- `GET /scim/v2/Users?filter=externalId eq "<subject>"` finds an account.
  `userName eq "<email>"` also works. Other filters return `invalidFilter`.
- `PATCH /scim/v2/Users/{id}` changes `active`, the role, the email and
  `displayName`. Resending the current values writes nothing.

An email held by a different account returns `409`. While there are no users,
only an active administrator can be created. Deactivating or demoting the last
active administrator returns `409`.

Deactivation revokes the user's browser sessions and MCP tokens. Reactivation
restores neither, and keeps the identity link. See
[docs/authentication.md](https://github.com/labstack/pitot/blob/main/docs/authentication.md#scim-provisioning)
for the full contract.

## Recover local sign-in

Run with the server's environment and data directory:

```sh
pitot login-link admin@example.com
# For Docker:
docker exec pitot pitot login-link admin@example.com
```

The URL is printed to stderr, expires in 15 minutes and works once. Issuance
and redemption are audited. The command supports an online server but refuses
missing or suspended users, a missing control database and OIDC mode without
[`--break-glass`](#oidc-break-glass-recovery).

Creating a local user without SMTP returns `201`,
`invite_delivery: "not_configured"` and `login_link_required: true`. Mint a link
for that active user and deliver it through a trusted channel. If configured
SMTP fails delivery, the API reports an error. Repair SMTP after regaining access.

If the only administrator is suspended or has an unusable address, stop Pitot
and back up the control database. Repair that existing account's email or status
in SQLite, preserving its ID and owned data. Restart and mint a login link for
the active account.

## OIDC break-glass recovery

If the identity provider is unavailable, run this with the server's environment
and data directory to issue a one-time link for an active administrator:

```sh
pitot login-link --break-glass admin@example.com
# For Docker:
docker exec pitot pitot login-link --break-glass admin@example.com
```

Set `PITOT_AUTH_CODE_SECRET` with at least 32 characters before you need this.
OIDC mode does not require it at startup, but without it the command fails and
the server keeps login links disabled. Pitot starts while the provider is
unavailable, so this also works after a restart during the outage.

The command refuses other roles, suspended users and local mode. The URL expires
in 15 minutes and works once. In OIDC mode, the server accepts only these links
and re-checks that the account is still an active administrator. Issuing and
redeeming write the distinct audit events `login_link.break_glass_issued` and
`login_link.break_glass_redeemed`. If the redemption event cannot be written,
Pitot creates no session and the request fails; the link is spent, so issue a
new one.

`/api/auth/link/verify` is rate limited per client address. Behind a reverse
proxy, set `PITOT_TRUSTED_PROXY_CIDRS`; otherwise every request shares the
proxy's address and one limit, and other traffic can block recovery.

## Review MCP connections

Open **Connect telemetry → Connected agents** in the account menu to review and disconnect one OAuth client.
Disconnecting revokes that client's access and refresh tokens for your account.
Reconnect by authorizing it again. Other clients and browser sessions remain
connected. See [MCP connections](/docs/guides/mcp#disconnect-an-agent) for API routes.

## Access boundaries

See [namespace access limits](/docs/reference/capabilities#operating-limits).
Dashboard access is scoped to the owner for every role, including administrators.