Authentication in CI
The desktop app signs you in with a browser. A pipeline has no browser and no user session, so the CLI resolves authentication differently, without changing anything about your requests.
The key idea: your requests never change. A request saved with auth: { type: "oauth", configId: "my-entra" } works identically in the app and in the CLI. What changes is where the token comes from.
How the CLI resolves a token
For every request that needs a token, the CLI tries, in order:
- Explicit override: a token passed with
--token <configId>=<token>or theREQUESTO_TOKEN_*environment variable. Used as-is, applied exactly as the app would apply it. - Non-interactive flows: for OAuth configs using client credentials or password grants (or with a usable refresh token), the CLI fetches/refreshes the token itself, using the client secret you inject.
- Fail fast with guidance: interactive flows (authorization code + PKCE, implicit) can't open a browser. The CLI fails that request with instructions instead of hanging or silently skipping.
Which option do I need?
Does your request use OAuth?
├── No (bearer / basic / api-key) → pass values as variables: --var apiToken=...
├── Yes, my IdP gives me a token in the pipeline (Entra ID, cloud CLIs)
│ → --token <configId>=<token> (option 1)
├── Yes, confidential client with a client secret
│ → --oauth-secret <configId>=<secret> (option 2)
└── Yes, interactive flow, but I have a long-lived refresh token
→ --refresh-token <configId>=<token> (option 2)Option 1: Pass a ready token (--token)
The most robust pattern. Your pipeline is often already authenticated to your identity provider, let it fetch the token, and hand it to Requesto.
Microsoft Entra ID + Azure DevOps
Entra app registrations created as SPA / public client platforms only support PKCE, a browser flow, which is exactly what pipelines can't do. Instead, fetch a token for your API's service principal and inject it:
- task: AzureCLI@2
name: token
inputs:
azureSubscription: my-service-connection
scriptType: bash
script: |
echo "##vso[task.setvariable variable=ENTRA_TOKEN;issecret=true]$(az account get-access-token --resource api://my-api --query accessToken -o tsv)"
- script: |
docker run --rm \
-v $PWD/.requesto:/work/.requesto \
-e REQUESTO_TOKEN_MY_ENTRA \
ghcr.io/t3rr11/requesto-cli:latest \
run --environment staging --reporter junit:/reports/junit.xmlThe environment variable REQUESTO_TOKEN_MY_ENTRA corresponds to the OAuth config with id my-entra (matching is case-insensitive; any character that isn't a letter, digit or underscore becomes _).
Microsoft Entra ID + GitHub Actions
- uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Get token
id: token
run: |
TOKEN=$(az account get-access-token --resource api://my-api --query accessToken -o tsv)
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Run Requesto tests
run: |
docker run --rm \
-v "$PWD/.requesto:/work/.requesto" \
-e ENTRA_TOKEN \
ghcr.io/t3rr11/requesto-cli:latest \
run --environment staging
env:
ENTRA_TOKEN: ${{ steps.token.outputs.token }}Or skip the indirection and mount a .env file: --var-file (see CLI Reference).
Option 2: Non-interactive OAuth flows
If your OAuth config uses the client credentials grant (a confidential client with a secret) the CLI can fetch and refresh tokens by itself. Provide the secret, which the app stores in the gitignored local/oauth-secrets.json, in CI, inject it instead:
docker run --rm \
-v "$PWD/.requesto:/work/.requesto" \
-e REQUESTO_OAUTH_SECRET_MY_API \
ghcr.io/t3rr11/requesto-cli:latest runor explicitly: --oauth-secret my-api=<secret>.
Notes:
- Token caching and refresh behave exactly as in the app. By default acquired tokens are kept in memory only; add
--persistto write them back to.requesto/local/oauth-tokens.json(useful when a cache volume is mounted). - The password grant works the same way when credentials are stored in the config's additional parameters.
- A refresh token can be seeded for an otherwise-interactive config:
--refresh-token my-entra=<refresh-token>(orREQUESTO_REFRESH_TOKEN_MY_ENTRA). Use a long-lived refresh token from a one-time interactive sign-in.
What happens when it can't authenticate
A request whose OAuth config needs a browser and has no override fails individually with a message like:
OAuth config "my-entra" uses the "authorization-code-pkce" flow, which requires
a browser and cannot run headless.
Options:
1. Pass a ready access token: --token my-entra=<accessToken> (or REQUESTO_TOKEN_* env var)
...The run continues with the remaining requests, the report marks the failure, and the exit code is 1.
Secrets checklist
- Never commit
local/, it's gitignored and contains secrets and token caches. - Inject secrets via your CI's secret store (
REQUESTO_*env vars or--var-filefrom a secret file). - Mask tokens in build logs (e.g.
echo "::add-mask::$TOKEN"in GitHub Actions). - Prefer short-lived, per-run tokens fetched by the pipeline over long-lived static secrets.