Sign in from the command line
Every admin caller over the network presents a JWT, and getting one is the caller’s business, pistra verifies tokens and mints none. That is the right split, and on its own it leaves an awkward gap. A person at a terminal has no obvious way to turn “I am in the directory” into a header, and that gap is where static tokens get reinvented.
pistra login closes it.
$ pistra login https://gw.exampleOpening your browser to sign in at corp
Signed in to https://gw.example as ada@example.com (via corp).The token expires at 2026-08-23T21:14:06+03:00.
$ pistra admin -url https://gw.example /admin/v1/configSet PISTRA_ADMIN_URL and both commands drop the URL.
Your credentials go to your identity provider and never to pistra. The gateway’s only part in this is one unauthenticated request asking where its provider is. Nothing here weakens the rule that there is no admin secret.
What the operator configures
Section titled “What the operator configures”A login block on the issuer, and an OAuth client registered at the
provider:
issuers: - name: corp url: https://login.example.com
admin: listen: 0.0.0.0:8485 policy_file: /etc/pistra/admin.cedar issuers: - issuer: corp audience: pistra-cli roles: claim: groups map: platform-admins: [admin] login: client_id: pistra-cli scopes: [profile, groups]Register pistra-cli as a public client: no secret, PKCE
required, and a loopback redirect. RFC 8252 says the authorization
server must accept any port on 127.0.0.1, and providers that let you
pin one should be left unpinned. Another program on the machine can be
sitting on a fixed port first.
A Keycloak realm with the public client, the group-membership mapper
that becomes the groups claim and the device grant switched on is
committed as ui/dev/idp/realm.json. Copy its client and mapper rather
than reconstructing them, but add the loopback redirect yourself. The
committed list carries the console’s /ui/callback only, because the
browser tests are the only thing that signs in at that realm.
Registering the client is one step of connecting a provider, and the rest, the issuer entry, the role mapping, and the second redirect the console needs, is Connect your identity provider.
There is no client secret anywhere in this, because a program on somebody’s laptop cannot keep one. PKCE covers that. It binds the authorization code to the process that asked for it, so a code intercepted on the way back cannot be redeemed by anybody else.
audience must be what your provider puts in the token’s aud. For an
ID token, the default, that is the client id, so the two are normally
the same string. Setting them differently without a reason produces a
token the gateway then refuses.
The block is optional and per-issuer. An issuer without one is not advertised. A workload-identity issuer, say, is for service accounts that already hold a token and have no browser to open.
Which flow runs
Section titled “Which flow runs”pistra login picks whichever flow suits the machine.
A browser is available, authorization code with PKCE, redirecting to a loopback listener. The browser opens, you sign in, the tab says you can close it.
There is no browser, the device grant. This is the case that matters. Over SSH, the alternative to a working sign-in is somebody pasting a long-lived token into a shell.
$ pistra login -device https://gw.example
To sign in at corp, visit:
https://login.example.com/activate
and enter the code:
WDJB-MJHT
Waiting...It picks the device flow on its own over SSH or with no display.
-device forces it, worth using when you would rather authorize on
your phone than in the browser this shell can reach. It needs the
provider to publish a device_authorization_endpoint. Not all do.
Afterwards
Section titled “Afterwards”The token is cached under your config directory, 0600 in a 0700
directory, keyed by gateway. Nothing else on the machine can read it and
nothing is shared between gateways.
$ pistra logout -listhttps://gw.example ada@example.com via corp valid until 2026-08-23T21:14:06+03:00While the provider issues refresh tokens, pistra admin renews the
session as it lapses and you never notice. Where it does not, the
message says so at sign-in rather than surprising you an hour later:
The token expires at 2026-08-23T21:14:06+03:00, and there is no refreshtoken, so signing in again is how it renews.pistra logout forgets this machine’s copy. It does not revoke
anything. The token stays valid at the provider until it expires, and an
incident that needs it actually dead needs the provider. pistra can
meanwhile take the caller’s access away at its own end, see
Suspend a key, provider or rule.
Several issuers
Section titled “Several issuers”A gateway can trust more than one. pistra login picks the only one
on offer and asks when there is a choice, because signing somebody in
somewhere they did not ask to be is not a default worth having:
$ pistra login https://gw.examplepistra: this gateway offers corp, partners; pick one with -issuerWhere the sign-in comes from
Section titled “Where the sign-in comes from”Run this command:
$ curl -s https://gw.example/admin/v1/login{"issuers":[{"name":"corp","issuer":"https://login.example.com", "client_id":"pistra-cli","scopes":["profile","groups"],"token":"id"}]}That endpoint is the one thing the admin API serves without a token, and it has to be. A client that must already be signed in to find out how to sign in is no use. Nothing in it is confidential, a public client’s id is public by design, and the issuer URL is on the front of every token it mints. It exists so nobody is handed an issuer URL, a client id and a scope list out of band. That is how a shared secret gets invented by accident.
A gateway with no login block anywhere answers an empty list.
When the provider needs something else
Section titled “When the provider needs something else”Some providers want an extra authorization parameter before they will
mint a token for the right audience, Auth0 wants audience, an
RFC 8707 server wants resource. Those go through verbatim:
login: client_id: pistra-cli extra: audience: https://pistra.example/api token: accesstoken: access presents the access token instead of the ID token, for a
provider whose JWT access tokens name the API rather than the client. If
neither applies to yours, leave both out.
What this is not
Section titled “What this is not”It is not break-glass. This flow depends on the identity provider being reachable, so it stops working on the day the provider is the problem. That is the local socket, and the two are complementary. A comfortable everyday sign-in keeps the break-glass door rare.