Secure JWT ID token generation for authenticating with third-party cloud providers and services. GitHub Actions provides OIDC (OpenID Connect) integration that allows workflows to authenticate with external systems without storing long-lived credentials.
The OIDC system in GitHub Actions generates short-lived JWT tokens that can be used to authenticate with cloud providers like AWS, Azure, Google Cloud, and other OIDC-compatible services. These tokens are cryptographically signed by GitHub and contain claims about the workflow context.
Generates a JWT ID token from the GitHub OIDC provider with optional custom audience.
/**
* Get JWT ID token from GitHub OIDC provider
* @param aud - Optional audience for which the ID token is intended
* @returns Promise resolving to a JWT ID token string
* @throws Error if OIDC is not available or token generation fails
*/
function getIDToken(aud?: string): Promise<string>;Usage Examples:
import { getIDToken, info, setSecret } from '@actions/core';
// Get default ID token (audience will be the repository URL)
try {
const defaultToken = await getIDToken();
setSecret(defaultToken); // Automatically mask in logs
info('Default OIDC token generated successfully');
} catch (error) {
setFailed(`Failed to get OIDC token: ${error.message}`);
}
// Get token with custom audience for AWS
const awsToken = await getIDToken('sts.amazonaws.com');
setSecret(awsToken);
info('AWS OIDC token generated');
// Get token with custom audience for Google Cloud
const gcpToken = await getIDToken('https://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/github');
setSecret(gcpToken);
info('GCP OIDC token generated');
// Get token for Azure
const azureToken = await getIDToken('api://AzureADTokenExchange');
setSecret(azureToken);
info('Azure OIDC token generated');The JWT ID token contains standard OIDC claims and GitHub-specific claims:
iss (Issuer): https://token.actions.githubusercontent.comaud (Audience): The specified audience or default repository URLsub (Subject): Repository and workflow informationiat (Issued At): Token creation timestampexp (Expiration): Token expiration timestamp (typically 1 hour)nbf (Not Before): Token validity start timerepository: Repository name (e.g., owner/repo)repository_owner: Repository ownerrepository_id: Unique repository identifierref: Git reference (branch/tag) that triggered the workflowworkflow: Workflow file namejob_workflow_ref: Reference to the workflow filerun_id: Unique workflow run identifierrun_number: Workflow run numberrun_attempt: Workflow run attempt numberactor: User who triggered the workflowevent_name: Event that triggered the workflow (push, pull_request, etc.)import { getIDToken, setSecret, exportVariable } from '@actions/core';
import { STSClient, AssumeRoleWithWebIdentityCommand } from '@aws-sdk/client-sts';
async function authenticateWithAWS() {
try {
// Get OIDC token for AWS
const oidcToken = await getIDToken('sts.amazonaws.com');
setSecret(oidcToken);
// Use OIDC token to assume AWS role
const stsClient = new STSClient({ region: 'us-east-1' });
const assumeRoleCommand = new AssumeRoleWithWebIdentityCommand({
RoleArn: 'arn:aws:iam::123456789012:role/GitHubActionsRole',
WebIdentityToken: oidcToken,
RoleSessionName: 'GitHubActions',
DurationSeconds: 3600
});
const response = await stsClient.send(assumeRoleCommand);
// Export AWS credentials as environment variables
exportVariable('AWS_ACCESS_KEY_ID', response.Credentials!.AccessKeyId!);
exportVariable('AWS_SECRET_ACCESS_KEY', response.Credentials!.SecretAccessKey!);
exportVariable('AWS_SESSION_TOKEN', response.Credentials!.SessionToken!);
// Mask the credentials
setSecret(response.Credentials!.AccessKeyId!);
setSecret(response.Credentials!.SecretAccessKey!);
setSecret(response.Credentials!.SessionToken!);
info('Successfully authenticated with AWS using OIDC');
} catch (error) {
setFailed(`AWS OIDC authentication failed: ${error.message}`);
}
}import { getIDToken, setSecret, exportVariable } from '@actions/core';
async function authenticateWithGCP() {
const projectNumber = '123456789';
const poolId = 'github-pool';
const providerId = 'github-provider';
try {
// Get OIDC token for Google Cloud
const audience = `https://iam.googleapis.com/projects/${projectNumber}/locations/global/workloadIdentityPools/${poolId}/providers/${providerId}`;
const oidcToken = await getIDToken(audience);
setSecret(oidcToken);
// Set up environment for gcloud CLI authentication
exportVariable('GOOGLE_APPLICATION_CREDENTIALS', ''); // Clear default
exportVariable('CLOUDSDK_AUTH_ACCESS_TOKEN', ''); // Will be set by gcloud
// The OIDC token can be used with gcloud auth login
info('OIDC token generated for Google Cloud Workload Identity');
info(`Use with: gcloud auth login --brief --cred-file=<(echo '${JSON.stringify({
type: 'external_account',
audience,
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
token_url: 'https://sts.googleapis.com/v1/token',
credential_source: {
format: {
type: 'json',
subject_token_field_name: 'value'
},
url: 'data:application/json,{"value":"' + oidcToken + '"}'
}
})}')`);
} catch (error) {
setFailed(`GCP OIDC authentication failed: ${error.message}`);
}
}import { getIDToken, setSecret, exportVariable } from '@actions/core';
async function authenticateWithAzure() {
try {
// Get OIDC token for Azure
const oidcToken = await getIDToken('api://AzureADTokenExchange');
setSecret(oidcToken);
// Azure CLI can use the OIDC token directly
exportVariable('AZURE_CLIENT_ID', process.env.AZURE_CLIENT_ID || '');
exportVariable('AZURE_TENANT_ID', process.env.AZURE_TENANT_ID || '');
exportVariable('AZURE_FEDERATED_TOKEN_FILE', '/tmp/azure-token');
// Write token to file for Azure CLI
const fs = require('fs');
fs.writeFileSync('/tmp/azure-token', oidcToken);
info('OIDC token configured for Azure authentication');
info('Use with: az login --service-principal --username $AZURE_CLIENT_ID --tenant $AZURE_TENANT_ID --federated-token $(cat $AZURE_FEDERATED_TOKEN_FILE)');
} catch (error) {
setFailed(`Azure OIDC authentication failed: ${error.message}`);
}
}import { getIDToken, setSecret, info } from '@actions/core';
async function authenticateWithCustomProvider() {
const customAudience = 'https://my-service.example.com';
try {
// Get OIDC token for custom service
const oidcToken = await getIDToken(customAudience);
setSecret(oidcToken);
// Use token with custom service API
const response = await fetch('https://my-service.example.com/auth/oidc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${oidcToken}`
},
body: JSON.stringify({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token: oidcToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt'
})
});
if (!response.ok) {
throw new Error(`Authentication failed: ${response.status} ${response.statusText}`);
}
const authData = await response.json();
setSecret(authData.access_token);
// Use the access token for API calls
exportVariable('API_ACCESS_TOKEN', authData.access_token);
info('Successfully authenticated with custom OIDC provider');
} catch (error) {
setFailed(`Custom OIDC authentication failed: ${error.message}`);
}
}When validating OIDC tokens on the receiving end, verify:
iss claim is https://token.actions.githubusercontent.comaud claim matches your expected audienceexp claim)sub claim matches expected repository/workflow patternimport { verify } from 'jsonwebtoken';
import { getPublicKey } from './github-keys'; // Fetch GitHub's public keys
async function validateGitHubOIDCToken(token: string, expectedAudience: string) {
try {
// Get GitHub's public key (implement key fetching/caching)
const publicKey = await getPublicKey();
// Verify and decode token
const decoded = verify(token, publicKey, {
issuer: 'https://token.actions.githubusercontent.com',
audience: expectedAudience
}) as any;
// Additional validation
if (!decoded.repository || !decoded.workflow) {
throw new Error('Missing required GitHub claims');
}
// Check repository allowlist
const allowedRepos = ['owner/allowed-repo1', 'owner/allowed-repo2'];
if (!allowedRepos.includes(decoded.repository)) {
throw new Error(`Repository ${decoded.repository} not allowed`);
}
return decoded;
} catch (error) {
throw new Error(`Token validation failed: ${error.message}`);
}
}OIDC token generation can fail for several reasons:
import { getIDToken, warning, setFailed } from '@actions/core';
async function robustOIDCTokenHandling() {
try {
const token = await getIDToken('sts.amazonaws.com');
setSecret(token);
return token;
} catch (error) {
if (error.message.includes('ACTIONS_ID_TOKEN_REQUEST_TOKEN')) {
setFailed('OIDC is not enabled for this repository. Enable it in repository settings.');
} else if (error.message.includes('ACTIONS_ID_TOKEN_REQUEST_URL')) {
setFailed('OIDC token service is not available. This may be a temporary issue.');
} else if (error.message.includes('Failed to get ID Token')) {
warning('OIDC token generation failed, possibly due to network issues. Retrying...');
// Implement retry logic
await new Promise(resolve => setTimeout(resolve, 5000));
return await getIDToken('sts.amazonaws.com');
} else {
setFailed(`Unexpected OIDC error: ${error.message}`);
}
}
}setSecret() to prevent token exposure in logsTo use OIDC tokens, ensure:
id-token: write permission in workflow fileExample workflow configuration:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC
contents: read # Required for checkout
steps:
- uses: actions/checkout@v3
- name: Authenticate with OIDC
run: |
# Your OIDC authentication code here