Authentication: Client Id/Secret¶
The Client Id/Secret test checks that your authorization server authenticates API clients correctly with the OAuth 2.0 client credentials grant (RFC 6749 §4.4). The target is usually a token endpoint such as Auth0, Okta, Microsoft Entra ID, Keycloak or your own API gateway.
Each run:
- Requests an access token with the stored client ID and secret.
- Checks that the response follows RFC 6749: the target issues a token or, if you configured it, refuses the client.
- Optionally presents the issued token as a Bearer token to a protected resource and checks the status.
sequenceDiagram
participant H as Helena
participant T as Token endpoint
participant P as Protected resource
H->>T: POST grant_type=client_credentials
T-->>H: {access_token, token_type, expires_in} or 4xx {error}
opt Resource URL set and a token was issued
H->>P: GET/POST with Authorization: Bearer <token>
P-->>H: status
end
Sidebar: Authentication → Client Id/Secret
Before you begin¶
- You need a client registered with the authorization server and allowed to use the client credentials grant.
- The token endpoint (and resource URL, if you use one) must be public HTTPS URLs.
Field reference¶
Target¶
| Field | Key | Required | Default | Supported values / notes |
|---|---|---|---|---|
| Token endpoint | token_endpoint |
✅ | — | Public HTTPS URL, up to 2048 characters. |
| Client ID | client_id |
✅ | — | Up to 255 characters. |
| Client secret | secret client_secret |
✅ (on create) | — | Up to 1000 characters. Encrypted, never shown again. On edit, leave it blank to keep the stored secret. You can replace the secret but not remove it, because the test cannot run without one. |
| Client authentication | auth_method |
✅ | client_secret_basic |
client_secret_basic: HTTP Basic Authorization header (RFC 6749 §2.3.1), which every server must support. client_secret_post: client_id and client_secret in the request body. |
| scope | scope |
— | Space-separated, up to 1000 characters, e.g. read:orders write:orders. |
|
| audience parameter | audience |
— | Not part of RFC 6749, but required by some providers (e.g. Auth0). Up to 500 characters. | |
| resource parameter | resource |
— | A resource indicator URI (RFC 8707). |
Basic authentication encoding
With client_secret_basic, Helena form-urlencodes the client ID and secret before Base64-encoding them, as
RFC 6749 §2.3.1 requires. Servers that skip the decoding step break on secrets containing : or %, and this
test exposes that.
Expected result¶
| Field | Key | Default | Supported values / notes |
|---|---|---|---|
| The target should | expected_outcome |
success |
Issue a token (success) or Refuse the client (rejection), e.g. for a revoked or disabled client. |
| HTTP status | expected_status |
— | 100–599. Blank: any 2xx on success, any 4xx on refusal. |
| token_type | expected_token_type |
— | Success only. Case-insensitive, e.g. Bearer. |
| error code | expected_error |
— | Refusal only. Exact match, e.g. invalid_client. |
| Specification checks | strict_spec_checks |
On | See What gets checked. |
Protected resource¶
| Field | Key | Default | Supported values / notes |
|---|---|---|---|
| Resource URL | resource_url |
— | Optional public HTTPS URL that requires the issued token, e.g. https://api.example.com/v1/me. |
| Method | resource_method |
GET |
GET or POST (sent without a body). |
| Expected HTTP status | expected_resource_status |
— | 100–599. Blank: any 2xx. |
The resource is called only when a token was issued. In refusal tests the step is skipped.
What gets checked¶
When the target should issue a token:
- The HTTP status is 2xx, or matches HTTP status.
- An access token was issued.
- With Specification checks on (RFC 6749 §5.1):
token_typeis present.Cache-Control: no-storeis present.expires_in, if present, is a positive integer.
- The optional token_type matches.
When the target should refuse the client:
- No token was issued.
- The HTTP status is 4xx, or matches HTTP status.
- With Specification checks on (RFC 6749 §5.2):
- The body contains an
error. - A
401returned toclient_secret_basicincludes aWWW-Authenticateheader.
- The body contains an
- The optional error code matches.
Protected resource: the status equals Expected HTTP status, or is 2xx when that is blank.
If the target issues a refresh token, a note is recorded but the run does not fail. RFC 6749 §4.4.3 says a refresh token should not be issued for this grant.
Reading the results¶
| Step | Shows |
|---|---|
| Build token request | URL, client authentication method, client ID and form parameters. The secret is fingerprinted. |
| Send token request | Status, selected headers (including WWW-Authenticate) and the body. Tokens are fingerprinted, and JWT access tokens are decoded. |
| Evaluate token response | Outcome, token_type, error, number of failed assertions and notes |
| Call protected resource | Method, URL, status, headers and body. Skipped when no token was issued, and absent when no Resource URL is set. |
Examples¶
Auth0 machine-to-machine application¶
| Field | Value |
|---|---|
| Token endpoint | https://YOUR_TENANT.auth0.com/oauth/token |
| Client ID / Client secret | The M2M application's credentials |
| Client authentication | client_secret_post |
| audience parameter | Your API identifier, e.g. https://api.example.com |
| Resource URL | Optional: an endpoint of that API that requires a token |
Microsoft Entra ID¶
| Field | Value |
|---|---|
| Token endpoint | https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token |
| Client authentication | client_secret_basic or client_secret_post |
| scope | https://graph.microsoft.com/.default |
| Resource URL | https://graph.microsoft.com/v1.0/applications?$top=1 (requires an application permission) |
Checking that a revoked client is refused¶
Create a second test with the revoked client's credentials:
| Field | Value |
|---|---|
| The target should | Refuse the client |
| error code | invalid_client |
| HTTP status | 401 |
| Specification checks | On, so the WWW-Authenticate header is checked as well |
Troubleshooting¶
| Symptom | Likely cause |
|---|---|
401 invalid_client on a client you know is valid |
Wrong Client authentication method. Try client_secret_post. Or the secret contains : or % and the server doesn't decode Basic credentials. |
Fails on Cache-Control: no-store |
The token endpoint doesn't send the header RFC 6749 requires. Fix the server, or turn off Specification checks. |
access_denied or invalid_scope |
The client isn't allowed the requested scope or audience. |
Run status error |
A missing secret, a URL that isn't public HTTPS, or a connection failure or timeout. |