A secure token exchange service that converts GitHub Actions OIDC tokens into GitHub App installation access tokens. This enables GitHub Action workflows to authenticate as a GitHub App for repository operations like creating PRs, issues, and comments.
GitHub Actions workflows can use OIDC tokens to authenticate with external services. This service converts those OIDC tokens into GitHub App access tokens, allowing workflows to act as a GitHub App without requiring the App's private key to be stored in the repository(s).
- Receives an OIDC token from a GitHub Actions workflow
- Validates the token with GitHub's public keys and an
aud
check - Creates a JWT signed with your app's private key and exchanges for an installation access token
- Returns a short-lived access token to perform operations as the GitHub App
sequenceDiagram
participant GHA as GitHub Actions
participant TE as Token Exchange
participant GitHub as GitHub API
GHA->>TE: POST /exchange with OIDC token
TE->>GitHub: Validate OIDC token
GitHub-->>TE: Token claims (repo, org, etc.)
TE->>GitHub: Get app installation for repo
GitHub-->>TE: Installation ID
TE->>TE: Create JWT with App private key
TE->>GitHub: Exchange JWT for access token
GitHub-->>TE: Installation access token
TE-->>GHA: Return access token
GHA->>GitHub: Use token for API calls
Run the token exchange service using Docker:
docker run -p 8080:8080 \
-e ALLOWED_AUDIENCE="your-audience" \
-e GITHUB_APP_NAME="Your App Name" \
-e GITHUB_APP_CLIENT_ID="your-app-client-id" \
-e GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
your-private-key-content
-----END RSA PRIVATE KEY-----" \
ghcr.io/mattmorgis/github-token-exchange:latest
Set these environment variables (see .env.example
):
ALLOWED_AUDIENCE
: The audience value your workflows will use in OIDC requestsGITHUB_APP_NAME
: Your GitHub App's nameGITHUB_APP_CLIENT_ID
: Your GitHub App's client IDGITHUB_APP_PRIVATE_KEY
: Your GitHub App's private key (RSA format)
Add this to your workflow to exchange OIDC tokens for GitHub App tokens:
jobs:
use-github-app:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- name: Get GitHub App Access Token
id: get-app-access-token
run: |
# Get OIDC token from GitHub Actions
OIDC_TOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=your-audience" | jq -r '.value')
# Exchange for GitHub App token
APP_TOKEN=$(curl -X POST https://your-service-url/exchange \
-H "Content-Type: application/json" \
-d "{\"token\": \"$OIDC_TOKEN\"}" | jq -r '.access_token')
echo "::add-mask::$APP_TOKEN"
echo "token=$APP_TOKEN" >> $GITHUB_OUTPUT
- name: Use GitHub App Access Token
env:
GH_TOKEN: ${{ steps.get-app-access-token.token }}
run: |
# Now you can use the token with GitHub CLI or API
gh api /user
Use ngrok to expose your local service for testing:
ngrok http 8080
Then use the ngrok URL in your GitHub Actions workflow.
This repository includes a workflow (.github/workflows/oidc-token.yaml
) that generates an OIDC token for testing. Add it to your repo and run it manually to get a token saved as an artifact.
To implement custom validation (e.g., restricting to specific repositories):
- Fork this repository
- Modify the validation logic in
app/auth.py
- Build and deploy your customized version
This project uses uv
for Python dependency management.
Copy the environment file and configure:
cp .env.example .env
# Edit .env with your GitHub App credentials
Create virtual env and install dependencies
uv sync
source .venv/bin/activate
Install pre-commit
pre-commit install
# Run development server with auto-reload
uv run fastapi dev app/main.py
# Run production server
uv run fastapi run
# Run tests
uv run pytest
# Type checking
uv run mypy .
# Linting & Formatting
uv run ruff check
uv run ruff check --fix # auto fix
uv run ruff format --check
uv run ruff format # auto format