or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

environment-state.mdindex.mdinput-output.mdjob-summaries.mdlogging-annotations.mdoidc-tokens.mdoutput-grouping.mdpath-utilities.mdplatform-detection.md
tile.json

oidc-tokens.mddocs/

OIDC Token Generation

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.

Overview

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.

Capabilities

Get ID Token

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');

Token Structure

The JWT ID token contains standard OIDC claims and GitHub-specific claims:

Standard Claims

  • iss (Issuer): https://token.actions.githubusercontent.com
  • aud (Audience): The specified audience or default repository URL
  • sub (Subject): Repository and workflow information
  • iat (Issued At): Token creation timestamp
  • exp (Expiration): Token expiration timestamp (typically 1 hour)
  • nbf (Not Before): Token validity start time

GitHub-Specific Claims

  • repository: Repository name (e.g., owner/repo)
  • repository_owner: Repository owner
  • repository_id: Unique repository identifier
  • ref: Git reference (branch/tag) that triggered the workflow
  • workflow: Workflow file name
  • job_workflow_ref: Reference to the workflow file
  • run_id: Unique workflow run identifier
  • run_number: Workflow run number
  • run_attempt: Workflow run attempt number
  • actor: User who triggered the workflow
  • event_name: Event that triggered the workflow (push, pull_request, etc.)

Common Integration Patterns

AWS Integration

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}`);
  }
}

Google Cloud Integration

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}`);
  }
}

Azure Integration

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}`);
  }
}

Custom OIDC Provider Integration

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}`);
  }
}

Token Validation

When validating OIDC tokens on the receiving end, verify:

  1. Signature: Token is signed by GitHub's public key
  2. Issuer: iss claim is https://token.actions.githubusercontent.com
  3. Audience: aud claim matches your expected audience
  4. Expiration: Token is not expired (exp claim)
  5. Subject: sub claim matches expected repository/workflow pattern
  6. Custom claims: Repository, workflow, and other GitHub-specific claims

Example Token Validation (Node.js)

import { 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}`);
  }
}

Error Handling

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}`);
    }
  }
}

Security Considerations

  1. Always mask tokens: Use setSecret() to prevent token exposure in logs
  2. Use specific audiences: Avoid generic audiences when possible
  3. Validate token claims: Verify repository, workflow, and other claims on the receiving end
  4. Implement token rotation: Tokens are short-lived (1 hour) by design
  5. Restrict repository access: Use allowlists for repositories that can authenticate
  6. Monitor token usage: Log authentication events for security monitoring

Prerequisites

To use OIDC tokens, ensure:

  1. OIDC is enabled: Repository settings → Actions → General → Allow GitHub Actions to create and approve pull requests
  2. Workflow permissions: id-token: write permission in workflow file
  3. External configuration: Cloud provider/service is configured to trust GitHub's OIDC provider
  4. Network access: Workflow can reach GitHub's token service and target service

Example 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