Authentication
stare authenticates against CERN Keycloak using OAuth2 PKCE (Proof Key for
Code Exchange) — no passwords are ever stored, and tokens are kept in your
operating system's native credential store where available.
Login and logout
stare auth login # open CERN SSO in the browser, store tokens
stare auth status # print whether a valid token is stored
stare auth logout # revoke tokens server-side, then delete local storage
stare auth info # display decoded JWT claims from the stored token
stare auth export # print the offline refresh token (see Unattended use)
stare auth import # store an offline session from a refresh token on stdin
Pass --offline to stare auth login to request an offline session for cron
jobs and CI — see Unattended use.
auth info flags
| Flag | Description |
|---|---|
--access-token |
Print the raw PKCE access token |
--id-token |
Print the raw PKCE id token |
--exchange |
Show claims for the RFC 8693 exchanged token (requires STARE_EXCHANGE_AUDIENCE) |
auth logout sends a best-effort revocation request to the Keycloak /revoke
endpoint for both the access token and the refresh token before removing local
storage.
Token storage
By default stare stores tokens in the operating system's native credential
store:
| Platform | Backend |
|---|---|
| macOS | Keychain |
| Linux | Secret Service (GNOME Keyring / KWallet) |
| Windows | Credential Locker |
If no keyring backend is available (e.g. a headless CI machine), or a registered
backend is present but non-functional (e.g. a broken D-Bus Secret Service),
tokens fall back to a JSON file. Set STARE_TOKEN_STORAGE=file to always use
the file, or STARE_TOKEN_STORAGE=keyring to always use the keyring (backend
errors are then raised instead of falling back):
| Platform | Path |
|---|---|
| Linux | ~/.local/share/stare/tokens.json |
| macOS | ~/Library/Application Support/stare/tokens.json |
| Windows | %APPDATA%\stare\tokens.json |
Migration: On first run after upgrading to a version with keyring support, any existing plaintext token file is migrated to the keyring automatically and the file is deleted.
Token lifecycle
flowchart TD
A["stare auth login"] --> B["PKCE flow"]
B --> C["access + refresh + id tokens"]
C --> D["ID token validated with JWKS\n(signature, issuer, audience, expiry)"]
D --> E["stored in keyring\n(or file fallback)"]
F["stare <command>"] --> G{"access token\nnear-expiry?"}
G -->|no| H["use cached token"]
G -->|yes| I["refresh via refresh token"]
I -->|fails| J["delete stored tokens\nprompt re-login"]
K{"STARE_EXCHANGE_AUDIENCE set?"} -->|yes| L["exchange for audience-scoped token\n(RFC 8693)"]
L --> M["cached in memory\nre-exchanged 120 s before expiry"]
On login, stare validates the ID token using PyJWT against the CERN Keycloak
JWKS endpoint (STARE_JWKS_URL). Validation checks the signature, issuer, and
audience. If validation fails the tokens are not saved.
By default the access token is considered near-expiry when it has fewer than 60
seconds remaining (STARE_TOKEN_EXPIRY_MARGIN_SECONDS). Refresh tokens rotate
on use — if the Keycloak server rejects a refresh attempt (HTTP 4xx) the stored
token is deleted immediately and you are prompted to run stare auth login
again.
Unattended use (cron / CI)
A normal stare auth login session is tied to your CERN SSO session: once that
ends, the refresh token is rejected and stare asks you to log in again. For
scheduled jobs, request an offline session instead. Its refresh token
survives SSO logout and browser restarts.
1. Log in with an offline session
Offline sessions have no fixed expiry. They lapse only if unused for longer than the offline-session idle timeout configured on CERN Keycloak (not visible to the client), and every run renews them — so a job that runs regularly keeps working indefinitely.
An offline session outlives your SSO logout
Treat the stored token like a password. End the session with
stare auth logout on the host that holds it, which revokes it server-side.
--offline is deliberately not the default.
2. Store it where the job can read it
On macOS, cron jobs usually cannot unlock the login Keychain. Keep the session in the token file instead, and set the same variable in the job:
0 6 * * * STARE_TOKEN_STORAGE=file stare analysis search -q 'status = "Phase 0 Active"' > active.json
On a headless Linux host without a keyring the file is used automatically.
3. Move the session to another host
Log in on your laptop, then transfer the session:
stare auth export prints only the offline refresh token on stdout (the
warning goes to stderr, so it pipes cleanly). It refuses online sessions, which
would die with your SSO session. This is different from
stare auth info --access-token, which prints a short-lived access token
(minutes) that cannot be renewed.
stare auth import reads the token from stdin — or prompts with hidden input
when run in a terminal, for pasting — and redeems it immediately, so a bad or
expired token fails at import time rather than on the first scheduled run. A
rejected import leaves any session already on that host untouched.
One session, one host
Keycloak rotates refresh tokens on every use. Once the new host refreshes,
the copy left behind may be rejected on its next refresh. --move deletes
the local copy (without revoking it) so the session lives in exactly one
place. Run stare auth login --offline again on any host that needs its
own session.
--move is not transactional
--move deletes the local copy before the token reaches the other host. If
the transfer or the remote stare auth import fails, the exported session
stays valid server-side, but this host can no longer revoke it:
stare auth logout only revokes tokens that are still stored. Running
stare auth login --offline again creates a new, separate session; it does
not recover or revoke the orphaned one, which lapses only after the server's
offline-session idle timeout.
When the transfer might fail, export without --move, confirm that
stare auth import succeeded on the other host, and only then drop the
local copy:
The same operations are available from Python via
TokenManager: login(offline=True),
get_session_info(), export_refresh_token(move=...), and
import_refresh_token(token).
Token exchange (RFC 8693)
Some Glance API endpoints require an audience-scoped token rather than the raw
PKCE access token. Set STARE_EXCHANGE_AUDIENCE to enable:
export STARE_EXCHANGE_AUDIENCE=atlas-analysis-api
stare analysis search --query 'referenceCode = ANA-HION-2018-01'
The exchanged token is kept in memory only — never written to disk or keyring.
It is re-exchanged automatically when fewer than
STARE_EXCHANGE_TOKEN_BUFFER_SECONDS (default 120 s) remain before expiry.
Concurrency safety
Multiple stare processes (or threads) running simultaneously are safe. A
threading.Lock guards in-process refresh races; a filelock.FileLock on the
token path guards cross-process races. If two processes race on a token refresh,
only one performs the refresh and both use the new token.
Direct token injection
For short-lived scripts that already have an access token, inject it directly. Access tokens expire within minutes and are never refreshed on this path, so for cron jobs and recurring CI use an offline session instead.
When token= is provided, TokenManager is bypassed entirely — no token file
is read, written, or refreshed.
Security properties
| Property | Verified | Notes |
|---|---|---|
| ID token signature | Yes | RS256 via JWKS |
| ID token issuer | Yes | must match STARE_ISSUER |
| ID token audience | Yes | must match STARE_CLIENT_ID |
| ID token expiry | Yes | validated by PyJWT |
| Access token (display only) | No | decoded for display, not verified |
| Callback Host header | Yes | rejects requests from unexpected origins (DNS rebinding protection) |
| PKCE state parameter | Yes | CSRF protection |
| Refresh token rotation | Handled | stale token deleted on 4xx |
The auth info command decodes the stored token for display purposes only,
without verifying the signature. Security decisions must not rely on auth info
output.