Use GitHub Actions as a Workload Identity Provider by exchanging a GitHub-issued OIDC token for a short-lived OpenAI access token. This lets workflows authenticate to the OpenAI API without storing a long-lived API key in GitHub secrets.
GitHub can mint a signed OIDC JWT for a workflow job that has id-token: write permission and requests an identity token. OpenAI validates the token issuer, audience, signature, and mapping attributes before issuing an OpenAI access token.
Setting up GitHub Actions
Grant the workflow or job permission to request a GitHub OIDC token:
1
2
3
permissions:
id-token: write
contents: readThe id-token: write permission lets the job request an OIDC JWT. It does not grant write access to repository contents. The contents: read permission is needed by actions/checkout.
Request the token with the exact audience configured in your OpenAI Workload Identity Provider. Custom JavaScript actions can call core.getIDToken("your-wif-audience"); shell steps can call GitHub’s OIDC request URL directly. Audience values containing reserved URL characters, such as https://api.openai.com/v1, should be URL encoded before being appended to the request URL:
1
2
3
4
5
AUDIENCE="https://api.openai.com/v1"
ENCODED_AUDIENCE=$(jq -rn --arg audience "$AUDIENCE" '$audience | @uri')
TOKEN=$(curl -sSf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${ENCODED_AUDIENCE}" | jq -r .value)Important GitHub OIDC claims include:
iss: The token issuer. For GitHub Actions, this ishttps://token.actions.githubusercontent.com.aud: The audience value requested by the workflow. Configure OpenAI to require the exact value you request, such asyour-wif-audienceorhttps://api.openai.com/v1.sub: The main subject string. GitHub builds it from workflow metadata such as repository, branch, tag, pull request, or environment.repository: The repository running the workflow, such asmy-org/my-repo.repository_owner: The organization or user that owns the repository, such asmy-org.ref: The Git ref that triggered the workflow, such asrefs/heads/mainorrefs/tags/v1.0.0.workflow: The workflow claim. Use the actual claim value emitted by GitHub, such asdeployif that is the workflow claim in your job.workflow_ref: The workflow file path and ref, such asmy-org/my-repo/.github/workflows/deploy.yml@refs/heads/main.environment: The GitHub environment name, such asproduction, when the job uses an environment.run_id,run_number,run_attempt, andjob_workflow_ref: Run and job identifiers that can help with auditing or more advanced trust rules.
For the full claim list and subject formats, see GitHub’s OpenID Connect reference.
Verify the token
Before configuring workload identity federation, decode a sample GitHub OIDC token in the workflow runner and inspect its claims. After requesting the token in a workflow step:
1
2
3
4
5
6
7
8
9
TOKEN="$TOKEN" python3 - <<'PY'
import base64
import json
import os
payload = os.environ["TOKEN"].split(".")[1]
payload += "=" * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
PYThis command decodes the JWT payload without verifying the token signature. Use a local decoder for production tokens, and avoid pasting production tokens into third-party tools. Never log the raw GitHub OIDC token or the exchanged OpenAI access token.
A decoded GitHub Actions OIDC token will look similar to:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"iss": "https://token.actions.githubusercontent.com",
"aud": "https://api.openai.com/v1",
"sub": "repo:my-org/my-repo:environment:production",
"repository": "my-org/my-repo",
"repository_owner": "my-org",
"ref": "refs/heads/main",
"workflow": "deploy",
"workflow_ref": "my-org/my-repo/.github/workflows/deploy.yml@refs/heads/main",
"environment": "production",
"run_id": "1234567890",
"run_attempt": "1"
}Use the decoded payload to compare the token you received with the issuer, audience, and mapping values configured in OpenAI. Most configuration issues are visible in the iss, aud, repository, ref, and workflow_ref claims before you exchange the token.
Setting up workload identity federation
Create a Workload Identity Provider in OpenAI for GitHub Actions, then add a service account mapping that matches the GitHub workflow claims you trust.
Configure the Workload Identity Provider first, then create the service account mapping.
Set up the Workload Identity Provider
-
Create the Workload Identity Provider. Set Name to a unique value, such as
github-actions-prod. Use Description, such asProduction GitHub Actions workflows, to help admins identify the provider. -
Set the issuer and audience. Set OIDC Issuer URL to
https://token.actions.githubusercontent.com. Set Audience to the exact audience your workflow requests, such asyour-wif-audienceorhttps://api.openai.com/v1. -
Use GitHub OIDC discovery. Leave Use uploaded JWKS for token verification disabled. OpenAI uses GitHub’s OIDC discovery metadata and JWKS to verify the GitHub-signed token.
-
Add attribute transformations only if you need derived mapping attributes. Raw GitHub claims such as
repository,ref, andworkflowcan be used directly in mapping assertions. If you create derived attributes, the dashboard applies theopenai.prefix automatically; for example, entergithub_repositorywith expressionassertion.repositoryto createopenai.github_repository. Raw token claims that already start withopenai.are ignored foropenai.mapping keys unless a matching transformation is configured.
Set up the service account mapping
-
Create a service account mapping. Set Name to a unique value within the Workload Identity Provider, such as
github-actions-main-deploy. Use Description, such asProduction deploy workflow on main, to explain which workflow can use the mapping. -
Add exact claim assertions. Add one Key and Value row for each GitHub claim that must match. OpenAI requires every configured row to match before it issues an access token. For a production deploy workflow, use assertions like:
iss == "https://token.actions.githubusercontent.com" aud == "https://api.openai.com/v1" repository == "my-org/my-repo" ref == "refs/heads/main" workflow_ref == "my-org/my-repo/.github/workflows/deploy.yml@refs/heads/main"Prefer
workflow_refoverworkflowfor privileged mappings because admins usually intend to trust a specific workflow file path and ref. Workflow names can be renamed, and multiple workflow files can share the same name.In the mapping UI, enter these as key/value rows, such as Key
repositorywith Valuemy-org/my-repo, Keyrefwith Valuerefs/heads/main, and Keyworkflow_refwith Valuemy-org/my-repo/.github/workflows/deploy.yml@refs/heads/main. If the job uses a GitHub environment, also add Keyenvironmentwith Valueproduction.Caution: Avoid overly broad mappings, such as trusting only
repository_owner == "my-org", unless every repository in that owner namespace should be able to mint OpenAI access tokens. -
Choose the OpenAI target. Set Project to the OpenAI project that owns the target service account. Set Service account to the OpenAI service account the GitHub workflow can use, such as
github-actions-prod-deploy. -
Narrow API permissions if needed. Select appropriate Permissions such as
api.model.requestandapi.vector_store.readto further narrow access tokens minted from this mapping. Leave permissions blank to avoid adding a WIF-specific scope restriction; the token still authorizes as the mapped service account.
Using the token in a workflow
Configure your OpenAI SDK client to request a GitHub OIDC token and exchange it for an OpenAI-issued access token.
The workflow must grant id-token: write permission and pass the workload identity federation settings to the SDK code. The SDK requests the GitHub OIDC token from the ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN environment variables that GitHub exposes to the job, then uses the exchanged OpenAI access token to authenticate API requests.
For example, run your application code from a workflow like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name: deploy
on:
push:
branches:
- main
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Run OpenAI SDK code
env:
OPENAI_WIF_AUDIENCE: ${{ vars.OPENAI_WIF_AUDIENCE }}
OPENAI_IDENTITY_PROVIDER_ID: ${{ vars.OPENAI_IDENTITY_PROVIDER_ID }}
OPENAI_SERVICE_ACCOUNT_ID: ${{ vars.OPENAI_SERVICE_ACCOUNT_ID }}
run: node ./scripts/call-openai.jsStore OPENAI_WIF_AUDIENCE, OPENAI_IDENTITY_PROVIDER_ID, and OPENAI_SERVICE_ACCOUNT_ID as GitHub Actions variables. They identify the provider and service account but are not bearer credentials.
The following examples initialize an OpenAI client with a custom subject token provider. The provider requests a GitHub OIDC token for the configured audience and uses it as the subject token for workload identity federation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import OpenAI from "openai";
import type { SubjectTokenProvider } from "openai/auth";
const identityProviderId = process.env.OPENAI_IDENTITY_PROVIDER_ID;
const serviceAccountId = process.env.OPENAI_SERVICE_ACCOUNT_ID;
const audience = process.env.OPENAI_WIF_AUDIENCE;
const requestURL = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
if (
!identityProviderId ||
!serviceAccountId ||
!audience ||
!requestURL ||
!requestToken
) {
throw new Error(
"Set OPENAI_IDENTITY_PROVIDER_ID, OPENAI_SERVICE_ACCOUNT_ID, OPENAI_WIF_AUDIENCE, and run inside GitHub Actions with id-token: write"
);
}
function githubActionsOIDCTokenProvider(
requestURL: string,
requestToken: string,
audience: string
): SubjectTokenProvider {
return {
tokenType: "jwt",
getToken: async () => {
const url = new URL(requestURL);
url.searchParams.set("audience", audience);
const response = await fetch(url, {
headers: { Authorization: `bearer ${requestToken}` },
});
if (!response.ok) {
throw new Error(
`Failed to request GitHub OIDC token: ${response.status} ${response.statusText}`
);
}
const body = (await response.json()) as { value?: string };
if (!body.value) {
throw new Error("GitHub OIDC token response did not include a value.");
}
return body.value;
},
};
}
const client = new OpenAI({
workloadIdentity: {
identityProviderId,
serviceAccountId,
provider: githubActionsOIDCTokenProvider(requestURL, requestToken, audience),
},
});
const response = await client.responses.create({
model: "gpt-4.1-mini",
input: "Say hello from GitHub Actions workload identity federation.",
});
console.log(response.output_text);GitHub Actions best practices
- Use environment protections for production deployments. Require approvals or branch restrictions before workflows can access production OpenAI resources.
- Restrict mappings by repository. Match on repository-specific claims whenever possible instead of allowing access from all repositories within an organization.
- Restrict mappings by branch or workflow. Consider matching claims such as
repository,ref,environment, orworkflow_refto limit token issuance. - Use separate OpenAI service accounts for CI/CD and production workloads. Build pipelines often require different permissions than deployed applications.
- Avoid granting access to pull requests from untrusted forks. Forked pull requests may execute attacker-controlled code and should not receive production credentials.
- Use short-lived exchanges. GitHub OIDC tokens are intended for ephemeral authentication and should be exchanged only when needed.
- Audit repository ownership changes. Repository transfers, renames, and permission changes can affect the security assumptions behind existing mappings.
- Prefer exact claim matching. Match on claims such as
repository,ref, andenvironmentinstead of relying on organization-wide trust relationships.