Automate auth
The goal is that no request ever carries a pasted token. Set a provider up once and every request that references it authenticates itself.
Background: Auth provider and Sender.
Pick a kind
Section titled “Pick a kind”| Your situation | Use |
|---|---|
| Keycloak, Clerk, Auth0, Firebase, or Supabase | integration |
| A login endpoint that returns a token in the body | request |
| Anything else — a handshake, a signed assertion, two calls | script |
Integration
Section titled “Integration”Create the provider, choose the identity provider, and fill in its non-secret config — issuer or base URL, realm, client id, scope, domain. Client secrets and passwords are stored as keychain entries and referenced by name.
keycloak and auth0 take a grant of password or client_credentials. firebase and
supabase exchange email and password for a token.
authProviders: - name: users-api kind: integration integration: keycloak config: baseUrl: https://sso.example.com realm: acme clientId: test-harness grant: password secretRefs: - users-api.CLIENT_SECRET senders: - id: admin@example.com label: Staging admin secretRef: users-api.admin.PASSWORDRequest
Section titled “Request”When the login is just an API call, describe it as one. The provider carries a whole request plus a single extraction saying where the token is.
authProviders: - name: legacy-login kind: request request: name: Login method: POST url: "{{baseUrl}}/auth/login" body: contentType: application/json content: '{"user":"svc","pass":"{{pw}}"}' extract: var: token from: body path: data.access_tokenThe request may use any protocol, so fetching a token over gRPC and using it on HTTP calls is ordinary rather than special.
Script
Section titled “Script”For everything else. Your JavaScript runs in the engine’s sandboxed QuickJS runtime with a
controlled fetch and returns { token, expiresIn? }.
async function authenticate({ env, config, secret, fetch, sender }) { const res = await fetch(`${env.baseUrl}/oauth/token`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "password", client_id: config.clientId, client_secret: secret("users-api.CLIENT_SECRET"), username: sender.id, password: secret(sender.secretRef), }).toString(), }); if (!res.ok) throw new Error(`login failed: ${res.status}`); const json = await res.json(); return { token: json.access_token, expiresIn: json.expires_in };}Returning expiresIn is worth doing — it is how the cache knows when to refetch. Full
contract: Auth script contract.
Say where the token goes
Section titled “Say where the token goes”Injection is a property of the provider, so it is not repeated on every request:
inject: target: header # header | query | cookie name: Authorization template: "Bearer {{token}}"Omit it entirely and you get Authorization: Bearer {{token}}. For an API-key style header,
set name: X-API-Key and template: "{{token}}".
Add senders
Section titled “Add senders”One provider, several identities. Each sender has an id, an optional label and tenant,
and a secretRef naming its own keychain entry.
Mark one as the default. A request can override it:
auth: provider: users-api sender: viewer@example.comThis is how you check an authorization rule: send the same endpoint as an admin and as a viewer and compare the statuses.
Use it
Section titled “Use it”Reference the provider from a request’s Auth tab, or set it once as the collection default so every request in the file inherits it:
defaults: auth: provider: users-apiChains inherit it too — a chain step that references a request carries that request’s auth.
Check it works
Section titled “Check it works”Test on the provider resolves a token and reports success with the value masked. Copy token hands you the raw JWT from the same cache your requests use, for when you need it in a terminal.
Troubleshooting
Section titled “Troubleshooting”The token is fetched on every send. The provider is not reporting an expiry. Return
expiresIn from a script, or check the integration’s config.
secret(...) throws. The keychain entry named in secretRefs or a sender’s secretRef
does not exist on this machine. Secrets are per-machine by design and never sync — set it
here.
It works for one identity and not another. A sender without a secretRef has no
credential. Check that each sender points at its own keychain entry.