CLI Reference¶
The kstlib auth command group provides authentication management from the command line.
Commands Overview¶
Command |
Description |
|---|---|
|
Validate a JWT token with cryptographic proof |
|
Authenticate with a provider |
|
Clear stored tokens |
|
Check authentication status |
|
Print access token |
|
Show user information |
|
List configured providers |
check¶
Validate a JWT token with cryptographic proof. Performs a full 6-step verification chain against the issuer’s published keys, providing independently verifiable evidence that a token is valid and spec-compliant. Works with any RSA-signed JWT (id_token or access_token) whose issuer exposes an OpenID Connect discovery endpoint.
kstlib auth check [PROVIDER] [OPTIONS]
Why This Exists¶
Some systems only support RS256 and reject tokens signed with stronger algorithms like RS512.
Instead of reporting their own limitation, they blame the Identity Provider configuration.
kstlib auth check provides irrefutable cryptographic proof that the token is valid,
shifting the burden of proof where it belongs.
Cryptographic Proof Chain¶
The verification follows the exact same process defined by the OpenID Connect Discovery specification. Any third party can reproduce these steps independently and reach the same conclusion.
Identity Provider (IDP) kstlib auth check
======================== ====================
1. IDP signs token 4. Fetch discovery document
with PRIVATE key GET {issuer}/.well-known/openid-configuration
│ │
▼ ▼
┌─────────────┐ 5. Fetch JWKS (public keys)
│ JWT Token │─ ─ ─ given to user ─ ─ ─ ─ ─► GET {jwks_uri}
│ header. │ │
│ payload. │ ▼
│ signature │ 6. Match key by kid,
└─────────────┘ convert JWK to PEM
│
2. IDP publishes PUBLIC key ▼
at JWKS endpoint 7. Verify signature
│ a. DECRYPT(public_key, signature)
┌──────▼──────────────────┐ = recovered_hash
│ /.well-known/ │ b. SHA-512(header.payload)
│ openid-configuration │ = computed_hash
│ │ c. recovered == computed? YES
│ ─► jwks_uri ──────────┐ │ │
└───────────────────────┘ │ ▼
│ 8. Validate claims
┌─────────────────────────▼ iss == expected issuer?
│ /jwks │ aud == expected audience?
│ { keys: [ │ exp > now - 5min?
│ { kid, kty, n, e } │ iat < now + 5min?
│ ]} │ │
└─────────────────────────┘ ▼
┌───────────────┐
3. ANYONE can fetch these ◄─ ─ ─ same keys ─ ─ │ VALID or │
public keys and verify │ INVALID │
└───────────────┘
The proof is irrefutable because:
The public key is published by the IDP itself at a well-known URL
RSA signature verification is a mathematical operation: either the signature matches the public key or it does not
There is no interpretation, no configuration, no ambiguity
Any party with network access to the IDP’s discovery endpoint can reproduce the exact same verification
If
kstlib auth checksays VALID, thenDECRYPT(public_key, signature) == SHA-512(header.payload), and no system can legitimately claim otherwise
Note
The verification uses the same public keys and same algorithm that any spec-compliant system (Keycloak, Azure AD, Okta, Auth0) uses. If the token passes this check, the token is valid per the JWT (RFC 7519) and OpenID Connect specifications. A system that rejects it has a limitation, not a reason.
Issuer vs JWKS Server (Delegated Trust)¶
A common objection is: “The issuer URL and the JWKS server are different hosts, so the token
is invalid.” This is incorrect. The OIDC Discovery specification
(OpenID Connect Discovery 1.0)
explicitly allows the jwks_uri to point to any URL. The issuer declares which server
hosts its public keys via the discovery document.
Issuer (iss claim) JWKS Server
https://auth.corp.com https://keys.corp.com
│ ▲
│ │
▼ │
GET /.well-known/openid-configuration
{ │
"issuer": "https://auth.corp.com", │
"jwks_uri": "https://keys.corp.com/jwks" ◄── explicit delegation
}
The issuer DECLARES: "my public keys are at keys.corp.com"
This is the SAME trust model as SSL/TLS certificate chains.
The chain of trust is:
The token
issclaim sayshttps://auth.corp.comThe discovery document at
https://auth.corp.com/.well-known/openid-configurationdeclaresjwks_uriThe JWKS at that URI contains the public key matching the token’s
kidThe signature verifies against that public key
Whether the JWKS is hosted on the same server, a CDN, a dedicated key server, or a cloud HSM is an infrastructure decision. The trust chain is intact because the issuer explicitly published the JWKS location in its own discovery document.
Claiming that “issuer != JWKS host” invalidates a token is like claiming that an SSL certificate signed by DigiCert is invalid because DigiCert is not the website owner. That is exactly how delegated trust works.
Arguments¶
Argument |
Description |
|---|---|
|
Provider name from config (optional, uses default) |
Options¶
Option |
Description |
|---|---|
|
JWT string to validate directly (bypasses cached token) |
|
Show full JWT header, payload, PEM key, and step details |
|
Output as JSON for automation |
|
Validate the access token instead of the id_token |
Tip
--token does not have a -t shortcut to avoid conflict with the global --timeout option.
Token Source Priority¶
When no --token is provided, check reads from the provider’s cached tokens:
id_token(preferred, contains identity claims)access_token(fallback if no id_token available)--access-tokenflag forces access token validation
Exit Codes¶
Code |
Meaning |
|---|---|
0 |
Token is cryptographically valid |
1 |
Token is invalid (bad signature, expired, wrong issuer, etc.) |
2 |
System error (network failure, missing dependencies, etc.) |
Examples¶
# Validate cached token for default provider
kstlib auth check
# Validate cached token for specific provider
kstlib auth check corporate
# Validate an explicit JWT string
kstlib auth check --token "eyJhbGciOiJSUzUxMiIs..."
# Full verbose output (header, payload, PEM key, discovery doc)
kstlib auth check corporate --verbose
# JSON output for CI/CD pipelines
kstlib auth check corporate --json
# Validate access token instead of id_token
kstlib auth check corporate --access-token
# Script: exit code check
kstlib auth check --token "$TOKEN" --json && echo "VALID" || echo "INVALID"
Output (Normal Mode)¶
╭────────────────── Token VALID ──────────────────╮
│ Token Type id_token │
│ Algorithm RS512 │
│ Key ID (kid) rsa-signing-key │
│ Key Fingerprint SHA256:a1b2c3d4e5f6... │
│ Issuer https://sso.company.com/realms/ │
│ Audience my-app │
│ Subject a1b2c3d4-5678-90ab-cdef │
│ Expires 58m 30s remaining │
╰─────────────────────────────────────────────────╯
✓ decode_structure: JWT has 3 parts, header and payload decoded
✓ discover_issuer: OIDC discovery successful
✓ fetch_jwks: JWKS fetched, 2 keys available
✓ extract_public_key: Public key extracted (RSA 4096-bit)
✓ verify_signature: Signature valid (RS512)
✓ validate_claims: All claims valid
╭──────────── Verification Instructions ──────────────╮
│ Third parties can independently verify this token: │
│ 1. Fetch discovery: GET https://sso.company.com/... │
│ 2. Fetch JWKS: GET https://sso.company.com/jwks │
│ 3. Verify signature with kid='rsa-signing-key' │
│ 4. Check claims: iss, aud, exp │
╰─────────────────────────────────────────────────────╯
Output (JSON Mode)¶
kstlib auth check --token "eyJ..." --json
{
"valid": true,
"token_type": "id_token",
"signature_algorithm": "RS512",
"key_id": "rsa-signing-key",
"key_fingerprint": "a1b2c3d4e5f6...",
"discovery_url": "https://sso.company.com/.well-known/openid-configuration",
"jwks_uri": "https://sso.company.com/jwks",
"header": {"alg": "RS512", "kid": "rsa-signing-key", "typ": "JWT"},
"payload": {"iss": "https://sso.company.com/", "sub": "user123", "aud": "my-app", "exp": 1700000000},
"steps": [
{"name": "decode_structure", "passed": true, "message": "..."},
{"name": "verify_signature", "passed": true, "message": "..."}
],
"error": null
}
Use Case: RS512 Algorithm Dispute¶
Some platforms only support RS256 for JWT signature verification. When a corporate IDP signs tokens with RS512, these systems report “invalid token signature” instead of acknowledging their algorithm limitation. Their support teams may also argue that the issuer URL differs from the JWKS host, which is a valid OIDC configuration (see above).
kstlib auth check resolves both arguments with verifiable evidence:
# 1. Login with corporate IDP (RS512 tokens)
kstlib auth login corporate
# 2. Generate cryptographic proof
kstlib auth check corporate --verbose
# 3. Export proof as JSON for the support ticket
kstlib auth check corporate --json > token-validation-proof.json
The verbose output proves:
Field |
Value |
What it proves |
|---|---|---|
Algorithm |
RS512 |
Token uses RS512, not RS256 |
Signature |
VALID |
Mathematically verified against IDP’s public key |
Discovery URL |
|
Issuer publishes its own key location |
JWKS URI |
|
JWKS host declared by the issuer itself |
Key ID (kid) |
Matches token header |
The exact key used to sign this token |
Claims |
iss, aud, exp all valid |
Token is not expired, correct audience |
Conclusion: The token IS valid per the JWT and OpenID Connect specifications. The consuming system rejects it because it does not implement RS512, not because the token or the IDP configuration is invalid. The issuer/JWKS host difference is standard delegated trust, not a misconfiguration.
Manual Verification (Without kstlib)¶
The entire verification can be performed manually with standard tools (curl, python, openssl).
This section serves as a step-by-step procedure that any third party can follow independently.
Step 1: Decode the JWT¶
A JWT is three base64url-encoded parts separated by dots: HEADER.PAYLOAD.SIGNATURE
# Given a token (e.g. from browser DevTools, a token endpoint, or kstlib auth token -q)
TOKEN="eyJhbGciOiJSUzUxMiIsImtpZCI6InJzYS1zaWduaW5nLWtleSJ9.eyJpc3MiOi..."
# Decode header (first part)
echo "$TOKEN" | cut -d. -f1 | base64 -d 2>/dev/null | python -m json.tool
{
"alg": "RS512",
"kid": "rsa-signing-key",
"typ": "JWT"
}
# Decode payload (second part)
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | python -m json.tool
{
"iss": "https://auth.corp.com/realms/main",
"sub": "a1b2c3d4-5678-90ab",
"aud": "my-app",
"exp": 1700000000
}
Key information from the header:
alg: the signing algorithm (RS256, RS512, etc.) - the server decides this, not the clientkid: which key was used to sign (Key ID)
Step 2: Fetch the issuer’s public keys¶
The issuer (iss claim) publishes its keys via the OpenID Connect discovery mechanism.
# 1. Fetch discovery document
ISSUER="https://auth.corp.com/realms/main"
curl -s "$ISSUER/.well-known/openid-configuration" | python -m json.tool
{
"issuer": "https://auth.corp.com/realms/main",
"jwks_uri": "https://keys.corp.com/realms/main/protocol/openid-connect/certs",
"id_token_signing_alg_values_supported": ["RS256", "RS512"]
}
# 2. Fetch JWKS (the actual public keys)
JWKS_URI="https://keys.corp.com/realms/main/protocol/openid-connect/certs"
curl -s "$JWKS_URI" | python -m json.tool
{
"keys": [
{
"kid": "rsa-signing-key",
"kty": "RSA",
"alg": "RS512",
"n": "0vx7agoebGc...",
"e": "AQAB"
}
]
}
Important
The jwks_uri may point to a different host than the issuer. This is by design.
The issuer explicitly declares where its keys are hosted via its own discovery document.
This is delegated trust, the same principle as SSL certificate chains.
The client has no authority to reject this delegation.
Step 3: Match the key and verify the signature¶
Match the kid from the JWT header with the corresponding key in the JWKS, then verify:
# verify_token.py - standalone verification script
import base64, json, hashlib
from cryptography.hazmat.primitives.asymmetric import padding, utils
from cryptography.hazmat.primitives import hashes
from authlib.jose import JsonWebKey
# 1. Split the token
token = "eyJhbGci..."
header_b64, payload_b64, signature_b64 = token.split(".")
# 2. Load the public key from JWKS (fetched in step 2)
jwk_data = {"kid": "rsa-signing-key", "kty": "RSA", "n": "...", "e": "AQAB"}
public_key = JsonWebKey.import_key(jwk_data).get_public_key()
# 3. The signed data is header + "." + payload (as-is, base64url encoded)
signed_data = f"{header_b64}.{payload_b64}".encode("ascii")
# 4. Decode the signature
sig_bytes = base64.urlsafe_b64decode(signature_b64 + "==")
# 5. Verify: the public key "decrypts" the signature to recover the hash,
# then compares it with SHA-512(signed_data)
header = json.loads(base64.urlsafe_b64decode(header_b64 + "=="))
hash_alg = hashes.SHA512() if header["alg"] == "RS512" else hashes.SHA256()
public_key.verify(
sig_bytes,
signed_data,
padding.PKCS1v15(),
hash_alg,
)
# If no exception is raised, the signature is VALID.
# This is a mathematical fact, not an opinion.
What this proves¶
IDP signed:
1. hash = SHA-512(header.payload)
2. signature = ENCRYPT(private_key, hash) --> only the IDP can do this
We verify:
1. recovered_hash = DECRYPT(public_key, signature) --> anyone can do this
2. computed_hash = SHA-512(header.payload) --> same algorithm as IDP
3. recovered_hash == computed_hash ? --> YES = VALID, the IDP signed this token
Same data, same algorithm, same key pair.
The signature is mathematically valid. There is no interpretation.
If a system rejects this token, the possible explanations are:
Symptom |
Actual cause |
|---|---|
“Invalid signature” |
The system does not support the algorithm (e.g. RS512) |
“Unknown issuer” |
The system is not configured to trust this issuer |
“Invalid key” |
The system refuses keys from a different host than the issuer |
None of these are token problems. They are client limitations.
Note
Who decides the security parameters?
The Authorization Server (IDP) is the authority. It decides:
Which algorithms to use for signing (
id_token_signing_alg_values_supported)Where to host its public keys (
jwks_uri)What claims to include in the token
The client (Relying Party) adapts to what the server advertises, not the other way around. This is the same model as TLS: the server proposes its cipher suites, the client picks one it supports. A client that only supports TLS 1.0 cannot blame the server for requiring TLS 1.3.
login¶
Authenticate with an OAuth2/OIDC provider.
kstlib auth login [PROVIDER] [OPTIONS]
Arguments¶
Argument |
Description |
|---|---|
|
Provider name from config (optional, uses default) |
Options¶
Option |
Description |
|---|---|
|
Minimal output |
|
Callback timeout in seconds (default: 120) |
|
Print URL instead of opening browser |
|
Manual mode: no callback server, paste code from redirect URL |
|
Re-authenticate even if already logged in |
Examples¶
# Login with default provider
kstlib auth login
# Login with specific provider
kstlib auth login corporate
# Login without opening browser (for SSH sessions)
kstlib auth login --no-browser
# Manual mode for corporate environments (port 443, smart card, etc.)
kstlib auth login --manual
# Force re-authentication
kstlib auth login --force
# Custom timeout for slow networks
kstlib auth login --timeout 300
What Happens¶
Standard mode (default):
Opens your browser to the provider’s login page
Starts a local callback server (default:
http://127.0.0.1:8400/callback)Waits for the OAuth redirect with authorization code
Exchanges the code for access/refresh tokens
Encrypts and stores tokens with SOPS
Manual mode (--manual):
Displays the authorization URL to copy
You open the URL in a browser (on any machine)
After authentication, the browser redirects to a URL with
?code=...You paste that URL (or just the code) back into kstlib
kstlib exchanges the code for tokens
Tip
Use --manual when the callback server cannot bind (port 443, corporate firewalls) or when authenticating requires a smart card on a different machine.
Manual Mode Example¶
$ kstlib auth login --manual corporate
╭─────────────── Manual Login ───────────────╮
│ 1. Copy the URL below and open it │
│ 2. Complete the authentication │
│ 3. Copy the redirect URL from your browser │
│ 4. Paste it below │
╰────────────────────────────────────────────╯
Authorization URL:
https://sso.corp.com/oauth2/authorize?response_type=code&client_id=...
After authentication, paste the redirect URL or code:
(paste URL or code): https://callback.corp.com?code=abc123&state=xyz
Exchanging authorization code for token...
╭──── OK ────╮
│ Successfully authenticated with corporate. │
╰────────────╯
logout¶
Clear stored tokens for a provider.
kstlib auth logout [PROVIDER] [OPTIONS]
Options¶
Option |
Description |
|---|---|
|
Minimal output |
Examples¶
# Logout from default provider
kstlib auth logout
# Logout from specific provider
kstlib auth logout corporate
status¶
Check authentication status for a provider.
kstlib auth status [PROVIDER] [OPTIONS]
Options¶
Option |
Description |
|---|---|
|
Single-line output |
Examples¶
# Check default provider status
kstlib auth status
# Check specific provider
kstlib auth status corporate
# Quiet mode for scripts
kstlib auth status -q
Output¶
╭─────────────────── Auth Status ───────────────────╮
│ Provider corporate │
│ Status Valid │
│ Token Type bearer │
│ Expires At 2026-01-03 15:30:00 UTC │
│ Expires In 1h 23m │
│ Refreshable Yes │
│ Scopes openid profile email │
╰───────────────────────────────────────────────────╯
token¶
Print the current access token (for use with other tools).
kstlib auth token [PROVIDER] [OPTIONS]
Options¶
Option |
Description |
|---|---|
|
Token only, no formatting |
Examples¶
# Print token for default provider
kstlib auth token
# Use with curl
curl -H "Authorization: Bearer $(kstlib auth token -q)" https://api.example.com
# Use with httpie
http https://api.example.com "Authorization: Bearer $(kstlib auth token -q)"
Warning
The token is printed to stdout. Be careful not to log it or expose it in shell history.
whoami¶
Show user information from the OIDC userinfo endpoint.
kstlib auth whoami [PROVIDER] [OPTIONS]
Options¶
Option |
Description |
|---|---|
|
Show name and email only |
|
Output raw JSON response |
Examples¶
# Show user info
kstlib auth whoami
# Quiet mode
kstlib auth whoami -q
# Output: John Doe <john@example.com>
# Raw JSON for scripting
kstlib auth whoami --raw
Output¶
╭────────────── User Info (corporate) ──────────────╮
│ Subject a1b2c3d4-5678-90ab-cdef-1234567890ab │
│ Name John Doe │
│ Username johnd │
│ Email john.doe@company.com │
│ Email Verified Yes │
│ Groups admin, developers │
╰───────────────────────────────────────────────────╯
Note
whoami only works with OIDC providers that expose a userinfo endpoint. OAuth2 providers (like GitHub) don’t support this command.
providers¶
List all configured authentication providers.
kstlib auth providers [OPTIONS]
Options¶
Option |
Description |
|---|---|
|
Names only, one per line |
Examples¶
# List all providers
kstlib auth providers
# Quiet mode for scripting
kstlib auth providers -q
Output¶
╭───────────────── Auth Providers ─────────────────╮
│ Name Type Issuer │
├──────────────────────────────────────────────────┤
│ corporate oidc https://sso.company.com │
│ dev oidc http://localhost:8080 │
│ github oauth2 (manual endpoints) │
╰──────────────────────────────────────────────────╯
Default provider: corporate
Exit Codes¶
Code |
Meaning |
|---|---|
0 |
Success |
1 |
General error |
2 |
Authentication required (not logged in) |
3 |
Configuration error |
Scripting Examples¶
Check if authenticated¶
if kstlib auth status -q >/dev/null 2>&1; then
echo "Authenticated"
else
echo "Not authenticated"
fi
Auto-login if needed¶
kstlib auth status -q || kstlib auth login
Get token for API call¶
TOKEN=$(kstlib auth token -q)
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data
Loop through providers¶
for provider in $(kstlib auth providers -q); do
echo "Status for $provider:"
kstlib auth status "$provider"
done