Skip to Content
Authentication

Authentication

Every Glow request needs a caller identity. Authentication is a single scheme — a bearer token attached as an HTTP header:

SchemeHeaderIssued byResolves to
BearerAuthAuthorization: Bearer <jwt>Keycloak (interactive) or service-account exchangeprofile + session

The CLI handles this automatically: glow login writes the bearer to ~/.config/glow/tokens.json, and every subsequent call attaches the Authorization header for you.

The bearer JWT is verified and resolved to a profile + session before the route handler runs.

Interactive flow (glow login)

glow login --instance-url https://my-school.example.com

What happens:

  1. The CLI hits GET /.well-known/openid-configuration on the instance to discover Keycloak endpoints.
  2. It spawns a localhost listener and opens your browser to the authorize_endpoint with a generated state + code_verifier (PKCE — no client secret needed for the public CLI client).
  3. Keycloak logs you in (or returns to your existing SSO session) and redirects to http://127.0.0.1:<port>/callback?code=…&state=….
  4. The CLI exchanges the code at token_endpoint for an access token
    • refresh token and writes both to ~/.config/glow/tokens.json, keyed by instance URL.

After login, every subsequent CLI call attaches Authorization: Bearer <access_token> automatically. The refresh token is used silently when the access token expires.

Service-account flow (--token)

For CI / scripted use, mint a service-account JWT once and pass it explicitly:

glow login --instance-url https://my-school.example.com \ --token "$SERVICE_ACCOUNT_JWT"

This skips the browser dance and stores the token directly. The JWT must be signed by the same Keycloak realm and carry a profile.is_service_account = true claim.

Server-side logout

glow logout

Fires GET /logout on the instance (which writes a logouts_entry row so the next request mints a fresh session) and then clears the local tokens.json. The server-side call is best-effort — the local clear always runs even if the server is unreachable.

Raw HTTP

The curl examples throughout these docs read two environment variables — your instance URL and your bearer token — so you can export them once and every example is copy-paste-ready:

export GLOW_INSTANCE_URL=https://your-school.example.edu export GLOW_TOKEN=$(cat ~/.config/glow/tokens.json | jq -r '.[].access_token' | head -1) # …or paste a token directly: export GLOW_TOKEN=eyJhbG...

GLOW_INSTANCE_URL is the same variable the CLI reads; GLOW_TOKEN is the JWT glow login already stored under ~/.config/glow/. With both set, the canonical request shape is:

curl -X POST $GLOW_INSTANCE_URL/persona/search \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $GLOW_TOKEN" \ -d '{}'

That single Authorization: Bearer header authenticates every endpoint — read and mutation paths alike.

Discovery endpoints

EndpointPurpose
GET /.well-known/openid-configurationOIDC discovery doc (issuer, endpoints)
GET /jwkspublic signing keys for verifying issued JWTs
GET /authorizeOAuth authorization endpoint (browser redirect)
GET /oidc_callbackOAuth callback (used by browser flows)
POST /tokencode → access token / refresh exchange
GET /userinfointrospect the bearer’s profile claims
GET /login / GET /logoutbare browser-flow entry points

These mirror the standard OIDC surface — most clients only need /.well-known/... to bootstrap.

Multiple instances

glow login --instance-url https://school-a.example.com glow login --instance-url https://school-b.example.com # tokens.json now has both; the --instance-url flag (or GLOW_INSTANCE_URL # env var) selects which one a given command uses. GLOW_INSTANCE_URL=https://school-b.example.com glow personas search

Sessions are listed in the CLI via the instance config under ~/.config/glow/. There’s no first-class glow sessions list command today — read the file directly if needed.

Service accounts

Service accounts are the non-interactive flavour of bearer auth: a long-lived JWT that a CI pipeline, cron job, or programmatic client attaches without ever opening a browser. Same Authorization: Bearer <jwt> shape as a human session — only the issuance path differs.

The recap (covered in Service-account flow above):

glow login --instance-url https://my-school.example.com \ --token "$SERVICE_ACCOUNT_JWT"

The CLI’s --token branch (save_token in src/auth.rs) writes the JWT straight into ~/.config/glow/tokens.json with no refresh_token and no expires_in — the server enforces lifetime via the JWT’s own exp claim.

Minting a service-account JWT

Today this is an operator-side step, done directly against Keycloak:

  1. In the realm’s Keycloak admin console, create (or pick) a user that represents the service identity (e.g. ci-runner@yourdomain).
  2. Add a profile.is_service_account = true claim via a user attribute
    • a protocol mapper, so it lands in the issued token. Server-side resolution in resolve_identity.py accepts the JWT the same way as a human bearer; the claim is what downstream policy uses to distinguish “machine” from “person” when it matters (e.g. skipping interactive consent prompts).
  3. Mint a token for that user — admin REST API (POST /auth/realms/<realm>/protocol/openid-connect/token) with the service account’s credentials, or via the admin console’s “Get access token” helper.

Honest gap: there is no first-class CLI helper for minting service-account JWTs today (no glow service-accounts create). The operator does it via Keycloak admin or a custom script. The CLI’s job ends at consuming an already-minted token via --token.

Scope, rotation, storage

  • TTL. Driven by the Keycloak client / user token lifespan setting. Default Keycloak access-token lifespan is 5 minutes; service-account tokens are typically configured for hours-to-days so the CI run completes without a refresh dance.

  • Rotation. Re-mint in Keycloak, update the CI secret, re-run glow login --token "$NEW_JWT". No refresh token is stored, so expiry is hard-stop — plan rotation cadence to match TTL.

  • Storage in CI. Treat the JWT as a bearer credential. Standard pattern:

    # GitHub Actions env: SERVICE_ACCOUNT_JWT: ${{ secrets.GLOW_SERVICE_ACCOUNT_JWT }} run: | glow login --instance-url "$GLOW_INSTANCE_URL" \ --token "$SERVICE_ACCOUNT_JWT" glow personas search

    Never check the token into the repo; never echo it in logs.

Last updated on