JSON APIs can issue a JSON Web Token JWT as an access credential. cURL can request a JWT from an API's login endpoint and use the returned access token for a protected request.

The login format belongs to the API provider. A JSON username-and-password endpoint that returns a JWT in access_token fits this method; OAuth and OpenID Connect endpoints may require a different authorization flow. The shell commands require Bash, jq, and cURL 7.82.0 or newer for --json.

A bearer token grants its holder access until expiry or revocation. Real credentials and tokens belong only on the provider's intended HTTPS endpoints, with shell tracing disabled and token responses kept out of shared logs. An OpenID Connect id_token describes the user's authentication to a client; it is not an interchangeable API access token.

Steps to request and use a JWT with cURL:

  1. Set the token endpoint, the protected endpoint, and the API username in Bash.
    $ AUTH_URL='https://api.example.net/jwt/token' \
      API_URL='https://api.example.net/jwt/protected' \
      API_USER='svc-metrics-reader'

    These URLs and the username are placeholders for your provider's documented values. The login and protected endpoints must belong to the same environment, and the issued access token must have the audience and scope required by the protected API. For an already issued token, see How to authenticate with a bearer token in cURL.

  2. Read the password without echoing it into the terminal.
    $ IFS= read -r -s API_PASSWORD

    A literal password in a command can be copied by shell history, terminal recordings, or process listings. The input stays hidden; -r preserves backslashes and IFS= preserves leading and trailing spaces.

  3. Request the JWT from the JSON login endpoint.
    $ AUTH_RESPONSE="$(
      printf '%s' "$API_PASSWORD" |
        jq -Rs --arg username "$API_USER" '{username: $username, password: .}' |
        curl --disable --silent --show-error --fail \
          --json @- "$AUTH_URL"
    )"

    jq JSON-encodes the username and password, including quotes and backslashes. The password reaches jq through standard input rather than a command-line argument. --json @- sends the encoded body from standard input with JSON Content-Type and Accept headers; it does not encode or validate JSON itself. --disable appears first so curlrc defaults cannot add hidden options, and --fail makes HTTP errors return a nonzero status.

  4. Inspect the token type and lifetime without displaying the access token.
    $ printf '%s\n' "$AUTH_RESPONSE" | jq '{token_type, expires_in}'
    {
      "token_type": "Bearer",
      "expires_in": 3600
    }

    The API should document whether these fields are present. expires_in normally gives the lifetime in seconds; a missing value appears as null and does not establish an expiry time. The access token must be intended for bearer use.

  5. Send the protected request with a nonempty access token.
    $ if ACCESS_TOKEN="$(printf '%s\n' "$AUTH_RESPONSE" |
      jq -er '.access_token | strings | select(length > 0)')"; then
      printf 'Authorization: Bearer %s\n' "$ACCESS_TOKEN" |
        curl --disable --silent --show-error --fail \
          --header @- \
          --write-out '\nHTTP %{http_code}\n' \
          "$API_URL"
    else
      printf 'No nonempty access_token; protected request skipped.\n' >&2
      false
    fi
    {"authenticated": true, "subject": "svc-metrics-reader", "scope": "metrics:read"}
    HTTP 200

    jq -e returns a failure status when the filter produces no value. strings rejects missing, null, and non-string fields, while select(length > 0) rejects an empty string. The conditional prevents the protected call on failure. A different provider-documented access-token field needs a matching selector, never a blind fallback to id_token. --header @- reads the bearer header from standard input, keeping the token out of cURL's command-line arguments.

    The expected protected payload and a successful HTTP status confirm that the API accepted the request. An HTML sign-in page or an error body does not prove authentication. For 401 Unauthorized, a fresh token and the expected endpoint, audience, and scope are the relevant checks; 403 Forbidden can indicate insufficient permissions.

    After the request batch finishes, unset AUTH_RESPONSE ACCESS_TOKEN API_PASSWORD API_USER clears these secrets from the current shell. It does not revoke the token or erase any saved logs.

Share this guide