Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Sign in to the dashboard

Three authentication modes, one of which is only valid on loopback. Pick one, mint a credential, and know what reaches disk.

How-to guideLast updated

On this page

On your laptop the dashboard needs no credential, because it is bound to loopback and anyone who can reach it is already you. The moment it binds anything else, that stops being true, and the binary will not let you pretend otherwise.

This page covers the three modes, which to pick, and how to create a credential in each. Who can do what once they are in is roles and permissions.

Pick a mode

dashboard.auth.mode takes one of three values.

Mode Credential Pick it when
off none the admin listener binds loopback and you are the only user. The default.
token a bearer token one operator, or a script, or a Grafana panel embedding a view
users a session cookie backed by a local users file more than one person, or you want per-person roles and an audit trail with names

Whichever you pick, the mode governs the whole admin plane: the UI, the management API, and the exports. There is no separate setting for the API.

Token mode

One shared secret, presented as a bearer token. Set the mode:

dashboard:
  enabled: true
  auth:
    mode: token

Then mint a token. It is printed once and only its SHA-256 hash reaches disk, so there is no command that will show it to you again:

$ tokentriage token new --role viewer --label "grafana panel"
token id: df48d71da95e75ea  role: viewer
This token is shown ONCE and cannot be recovered:
ttk_Z-WSSTQBOaiu-lRKMUIFbAwmBAlC2INel0hl99y0gDs

That capture is from a throwaway state directory that no longer exists. Yours will differ, and the value after ttk_ is the part you keep.

Present it as an Authorization header:

$ curl -s -H "Authorization: Bearer ttk_…" \
    http://127.0.0.1:9090/tt/api/v1/capabilities

token list shows what exists without showing any secret, and token revoke takes effect immediately:

$ tokentriage token list
ID                 ROLE       REVOKED  LABEL
df48d71da95e75ea   viewer     false    grafana panel

Give a token the lowest role that does the job, and a label that says what it is for. A --ttl makes it expire on its own, which is the better default for anything handed to a person rather than baked into a deployment.

Users mode

Local users with per-person roles. Passwords are hashed with argon2id and sessions are minted server-side, so the cookie carries no credential material.

dashboard:
  enabled: true
  auth:
    mode: users
    session_ttl: 12h        # default: 12h

Add a user. The password is read from stdin and never echoed, logged or written:

$ tokentriage user add alice --role operator
Password:
added user "alice" with role operator
$ tokentriage user list
NAME                     ROLE       CREATED
alice                    operator   2026-08-10

The tree also has passwd, set-role and remove. All of them read and write users.yaml under the dashboard state directory, which defaults to ~/.tokentriage/dash and is set by dashboard.state_dir. Once the dashboard is running you can do the same work from the Users panel in Settings, which requires the admin role.

Repeated failed logins are throttled per source address before the password check runs, so a spray costs the attacker time without costing your instance the work of hashing. A sustained run of failures is recorded in the audit trail.

What a denial looks like

Two different failures, and telling them apart saves time.

No credential, on a route that needs one gets a 401. Send one.

A credential whose role is below the route’s floor gets a 403 naming the role that was required. It is never a 404: hiding the existence of a route you cannot use would make the boundary impossible to reason about.

{"type":"/tt/api/problems/forbidden","title":"Forbidden","status":403,"detail":"route not permitted","code":"forbidden","request_id":"req_06754dd4d478f526"}

Every denial carries a request_id that also appears on the server’s log line for the same request, which is what lets you correlate a user’s screenshot with your own logs.

Where the credentials live, and what to back up

Everything is under dashboard.state_dir, default ~/.tokentriage/dash:

File Holds
users.yaml user names, roles, argon2id password hashes
tokens.json token ids, roles, labels, expiry, SHA-256 hashes
the audit log every attempted write, accepted or rejected, with its actor
the event ring the durable mirror of successful mutations

None of it is derived, so none of it rebuilds itself. Include the state directory in whatever backs up your ledger; backup and restore covers the rest of what a deployment holds.

What can go wrong

Validation rejects the config with a message about the admin bind. You have mode: off and a non-loopback server.admin. Choose token or users.

A minted token does not work. Check that the mode is token; a token presented to an instance in users mode is not a credential it accepts. Check the state directory too, since token new writes to whichever directory it was given and the server reads the one in its config.

Nobody can sign in after a restore. The state directory was not restored with the ledger. Mint a new token from the host to get back in, then re-add users.

You need to reach the API from a script and do not want a long-lived secret. Mint a token with --ttl and re-mint on a schedule, or use a share token if the script only needs one frozen query. Share a view covers the second option.