Authentication: OIDC WIF Client¶
The OIDC WIF Client test checks that your security token service (STS) or OAuth 2.0 token endpoint handles OpenID Connect workload identity federation correctly. Federation is how systems such as Google Cloud, Microsoft Entra ID, AWS IAM and Keycloak let an external workload swap its platform-issued ID token for their own credentials, with no stored secret.
Helena plays the whole workload side:
- The identity provider. Each test has its own Helena-hosted OIDC issuer with a public discovery document and JWKS. You configure your target once to trust that issuer.
- The workload. Each run mints a signed ID token from that issuer, presents it to your target with the exchange flow you choose, and checks the response.
With token variants (expired, wrong audience, forged signature, alg: none, …), the same setup also checks
that your target refuses tokens it must refuse.
sequenceDiagram
participant H as Helena (workload)
participant I as Helena issuer
participant T as Your STS / token endpoint
H->>H: Mint ID token (variant applied)
H->>T: Token request (chosen flow)
T->>I: GET /.well-known/openid-configuration
T->>I: GET /jwks
T->>T: Verify signature, iss, aud, exp, sub, conditions
T-->>H: Token, or 4xx error
H->>H: Assertions → Test Log
Sidebar: Authentication → OIDC WIF Client
Before you begin¶
- You need administrative access to the target, so you can register Helena's issuer as a trusted identity provider.
- The target must be able to fetch Helena's issuer URLs over HTTPS (
https://app.helena.dev/oidc/wif/…). Some targets need an allow-list entry. - The token endpoint must be a public HTTPS URL. See How Helena reaches your target.
Set it up¶
- Create the test. Choose the Exchange flow for your target, then fill in the Token endpoint and Audience (aud). Leave Token variant as Valid token. Save.
- Copy the trust details. The test page has a Trust setup panel that shows:
- Issuer URL:
https://app.helena.dev/oidc/wif/{test-uuid} - Subject (sub): by default
helena:env:{environment-uuid}:test:{test-uuid} - Audience (aud)
- Discovery document and JWKS URI
- Issuer URL:
- Configure the target to trust the issuer. Enter the issuer URL, the subject and the audience. The examples below cover Google Cloud, Entra ID and AWS.
- Select Run now. The run passes when the target issues a token.
- Add negative tests. Create more tests against the same target with variants such as Expired or Invalid signature. The target should switches to Refuse the token automatically.
Each test has its own issuer
Every test has a separate issuer URL and key set. A negative test therefore needs its issuer trusted on the target too. Otherwise it passes only because the issuer is unknown, not because the target checked the specific defect. For most variants, configure the target to trust each test's issuer URL and subject, just as you did for the valid test. The Untrusted issuer variant is the exception: it deliberately uses an issuer the target does not trust.
Field reference¶
Target¶
| Field | Key | Required | Default | Supported values / notes |
|---|---|---|---|---|
| Exchange flow | flow |
✅ | rfc8693 |
See Exchange flows. |
| Token endpoint / STS endpoint | token_endpoint |
✅ (OAuth flows) | — | Public HTTPS URL, up to 2048 characters. For AWS it is optional and defaults to https://sts.amazonaws.com/. You can use a regional endpoint such as https://sts.eu-west-1.amazonaws.com/. |
| audience parameter | sts_audience |
— | RFC 8693 only. The audience form parameter, i.e. the audience of the token you request (not the aud of the ID token). Google Cloud: //iam.googleapis.com/projects/…/providers/…. |
|
| resource parameter | resource |
— | RFC 8693 only. A URI of the target service. | |
| subject_token_type | subject_token_type |
urn:ietf:params:oauth:token-type:jwt |
RFC 8693 only. urn:ietf:params:oauth:token-type:jwt or urn:ietf:params:oauth:token-type:id_token. |
|
| requested_token_type | requested_token_type |
urn:ietf:params:oauth:token-type:access_token |
RFC 8693 only. Any token type URI. | |
| scope | scope |
— | OAuth flows. Space-separated scopes, up to 1000 characters. | |
| Client ID | client_id |
✅ (client assertion) | — | OAuth flows. Required for RFC 7523 client assertion (Entra: the application/client ID). Optional for the others. |
| Client secret | secret client_secret |
— | RFC 8693 and JWT bearer only. When set, it is sent with the client ID as HTTP Basic authentication (client_secret_basic). Encrypted, never shown again. On edit, tick Remove stored client secret to clear it. |
|
| Role ARN | role_arn |
✅ (AWS) | — | AWS only. arn:aws:iam::123456789012:role/NAME. |
| Role session name | role_session_name |
helena- + run ID |
AWS only. 2–64 characters from A–Z a–z 0–9 _ + = , . @ -. |
|
| Duration (seconds) | duration_seconds |
AWS default | AWS only. 900–43200. | |
| Extra form parameters | extra_params |
— | Up to 20 name/value pairs added to the request. They cannot override the token or secret parameters (assertion, client_assertion, subject_token, actor_token, WebIdentityToken, client_secret). |
|
| Extra request headers | extra_headers |
— | Up to 20 headers. You cannot set Authorization, Cookie, Host, Content-Type, Content-Length, Transfer-Encoding or Proxy-Authorization. |
Workload ID token¶
| Field | Key | Required | Default | Supported values / notes |
|---|---|---|---|---|
| Audience (aud) | audiences |
✅ | — | 1–10 values, one per line. A single value is sent as a string, several as an array. It must match what the target expects: e.g. the Google workload identity provider URL, api://AzureADTokenExchange or sts.amazonaws.com. |
| Subject (sub) | subject |
helena:env:{env-uuid}:test:{test-uuid} |
Up to 255 characters. The default does not change when you rename the test. | |
| Lifetime (seconds) | lifetime_seconds |
✅ | 300 |
60–3600. Time between iat and exp. |
| Signing algorithm | signing_alg |
✅ | RS256 |
RS256, RS384, RS512, ES256, ES384. Changing it generates a new signing key. See Signing keys. |
| Additional claims (JSON) | additional_claims |
{} |
A JSON object with up to 25 custom claims, e.g. {"repository": "acme/app"}. You cannot set iss, sub, aud, exp, iat, nbf or jti. |
|
| Token variant | token_variant |
✅ | valid |
See Token variants. |
Every token also carries two claims Helena adds itself: helena_environment and helena_test, containing the
environment and test UUIDs. Use them in your target's attribute conditions to trust only this test.
Expected result¶
| Field | Key | Default | Supported values / notes |
|---|---|---|---|
| The target should | expected_outcome |
success |
Issue a token (success) or Refuse the token (rejection). It is preselected to Refuse when you choose a negative variant. |
| HTTP status | expected_status |
— | 100–599. Blank: any 2xx on success, any 4xx on refusal. |
| token_type | expected_token_type |
— | Success only. Case-insensitive exact match, e.g. Bearer. |
| issued_token_type | expected_issued_token_type |
— | RFC 8693, success only. Exact match, e.g. urn:ietf:params:oauth:token-type:access_token. |
| error code / Error Code | expected_error |
— | Refusal only. Exact match on the OAuth error (e.g. invalid_grant) or the AWS error code (e.g. InvalidIdentityToken). |
| Specification checks | strict_spec_checks |
On | See What gets checked. |
Exchange flows¶
| Flow (UI label) | flow |
Request Helena sends | Typical targets |
|---|---|---|---|
| RFC 8693 Token Exchange | rfc8693 |
grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token=<ID token>, subject_token_type, plus optional audience, resource, scope, requested_token_type |
Google Cloud STS, Keycloak, most modern STSs |
| RFC 7523 client assertion | rfc7523_client_assertion |
grant_type=client_credentials, client_id, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer, client_assertion=<ID token>, scope |
Microsoft Entra federated identity credentials |
| RFC 7523 JWT bearer grant | rfc7523_jwt_bearer |
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer, assertion=<ID token>, scope |
Authorization servers that accept JWT bearer grants, including Helena's own service-account API |
| AWS STS AssumeRoleWithWebIdentity | aws_sts |
Action=AssumeRoleWithWebIdentity, Version=2011-06-15, RoleArn, RoleSessionName, WebIdentityToken=<ID token>, optional DurationSeconds |
AWS IAM roles with an OIDC identity provider |
Token variants¶
| Variant (UI label) | token_variant |
What Helena changes | A correct target… |
|---|---|---|---|
| Valid token | valid |
Nothing | issues a token |
| Expired | expired |
exp is 15 minutes in the past |
refuses |
| Not yet valid | not_yet_valid |
nbf is 15 minutes in the future |
refuses |
| Wrong audience | wrong_audience |
aud is https://wrong-audience.helena.invalid |
refuses |
| Untrusted issuer | wrong_issuer |
iss is https://untrusted-issuer.helena.invalid, signed with the real key |
refuses |
| Invalid signature | bad_signature |
Signed by an unpublished key under the real kid |
refuses |
| Unknown key ID (kid) | unknown_kid |
Real key, random kid |
refuses |
| Unsigned (alg: none) | alg_none |
No signature, alg: none |
refuses |
The 15-minute offsets are well beyond common clock-skew allowances (5 minutes at most), so a correct target always refuses them.
What gets checked¶
When the target should issue a token:
- The HTTP status is 2xx, or matches HTTP status.
- A token was issued.
- With Specification checks on:
token_typeis present (RFC 6749 §5.1).Cache-Control: no-storeis present (OAuth flows).issued_token_typeis present (RFC 8693 §2.2.1).- For AWS,
SubjectFromWebIdentityTokenequals the token'ssub.
- The optional token_type and issued_token_type match.
When the target should refuse the token:
- No token was issued.
- The HTTP status is 4xx, or matches HTTP status.
- With Specification checks on, an OAuth
error(RFC 6749 §5.2) or an AWSError/Codeis present. - The optional error code matches.
Reading the results¶
| Step | Shows |
|---|---|
| Mint ID token | Issuer, JWKS URI, variant, the JWT header and claims, and the token's fingerprint |
| Build token request | Flow, URL, form parameters (the token is fingerprinted) and client authentication method |
| Send token request | Status, selected response headers and the body. Issued credentials (access_token, id_token, refresh_token, AWS SecretAccessKey / SessionToken) are fingerprinted, and JWT access tokens are decoded for display. |
| Evaluate response | Outcome, error, number of failed assertions |
Signing keys¶
Each test signs with its own key pair. The kid is the key's RFC 7638 thumbprint, so it never changes for a given key and reveals nothing about it.
- Rotate signing key (on the test page) generates a new key. The old key stays in the JWKS for 24 hours, so targets that cache JWKS keep validating while they refresh.
- Changing the Signing algorithm also rotates the key.
- Discovery and JWKS responses can be cached for up to 5 minutes, and are limited to 120 requests per minute per IP.
- Disabling or deleting the test makes its issuer endpoints return
404, so the target can no longer fetch its keys. A target may still hold a cached copy for up to 5 minutes, or longer if it caches JWKS itself.
Examples¶
Google Cloud workload identity federation (RFC 8693)¶
-
Create the test:
Field Value Exchange flow RFC 8693 Token Exchange Token endpoint https://sts.googleapis.com/v1/tokenAudience (aud) https://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL/providers/PROVIDERaudience parameter //iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL/providers/PROVIDERsubject_token_type urn:ietf:params:oauth:token-type:jwtscope https://www.googleapis.com/auth/cloud-platform -
Create the provider in the pool:
-
Select Run now. Then add a test with the Expired variant to check that the pool refuses it.
Microsoft Entra ID federated identity credential (RFC 7523 client assertion)¶
| Field | Value |
|---|---|
| Exchange flow | RFC 7523 client assertion |
| Token endpoint | https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token |
| Client ID | The app registration's application (client) ID |
| Audience (aud) | api://AzureADTokenExchange |
| scope | https://graph.microsoft.com/.default |
In Entra, open the app registration, then Certificates & secrets → Federated credentials → Add credential →
Other issuer. Enter the test's Issuer URL and Subject (sub) from the test page, with audience
api://AzureADTokenExchange.
AWS IAM (AssumeRoleWithWebIdentity)¶
- In IAM, create an OIDC identity provider with the test's Issuer URL as the provider URL and
sts.amazonaws.comas the audience. -
Create a role whose trust policy allows
sts:AssumeRoleWithWebIdentityfor that provider, with these conditions: -
Create the test:
Field Value Exchange flow AWS STS AssumeRoleWithWebIdentity Role ARN The role you just created Audience (aud) sts.amazonaws.com
Troubleshooting¶
| Symptom | Likely cause |
|---|---|
A Valid token run is refused with invalid_grant or InvalidIdentityToken |
The target doesn't trust this test's issuer, or the subject or audience doesn't match. Compare the Mint ID token claims with the target's configuration. |
| Refused with an error about fetching keys or discovery | The target cannot reach https://app.helena.dev. Check egress rules and allow-lists. |
| A negative variant passes but you expected it to fail | That is the good result: the target refused the bad token. A run fails when the target accepts a token it should have refused. |
Run status error |
The token endpoint URL is not public HTTPS, or the connection failed or timed out. See the Send token request step. |
| Tokens stop validating after Rotate signing key | The target cached the JWKS for more than 24 hours. Shorten its JWKS cache. |