Skip to content

feat: Add action to get short-lived access token using OIDC#255

Merged
Mrtenz merged 5 commits into
mainfrom
mrtenz/get-token-action
May 29, 2026
Merged

feat: Add action to get short-lived access token using OIDC#255
Mrtenz merged 5 commits into
mainfrom
mrtenz/get-token-action

Conversation

@Mrtenz
Copy link
Copy Markdown
Member

@Mrtenz Mrtenz commented May 21, 2026

This adds a workflow to get a short-lived GitHub access token using token-exchange-service. Example workflow below:

name: Token exchange test

on:
  pull_request:

jobs:
  test:
    name: Test token exchange
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    steps:
      - uses: MetaMask/github-tools/.github/actions/get-token@v1
        id: token-exchange
        with:
          token-exchange-url: ${{ vars.TOKEN_EXCHANGE_URL }}
          permissions: |
            contents: read
            pull_requests: write

      - name: Comment on pull request
        uses: actions/github-script@v8
        with:
          github-token: ${{ steps.token-exchange.outputs.token }}
          script: |
            const prNumber = 1;
            const commentBody = 'This is a test comment using the exchanged token.';
            await github.rest.issues.createComment({
              ...context.repo,
              issue_number: prNumber,
              body: commentBody,
            });

Note

Medium Risk
Introduces a CI path that mints GitHub tokens with caller-chosen permissions; misuse or overly broad scopes in consuming workflows could expand blast radius, though tokens are short-lived and the action itself is additive.

Overview
Adds a new reusable composite GitHub Action Get Token (.github/actions/get-token) so workflows can obtain short-lived GitHub API access tokens via OIDC and an external token exchange service instead of long-lived PATs.

The action requests an OIDC token (api://token-exchange-service), POSTs it to {token-exchange-url}/api/exchange/token with newline-parsed scope: permission pairs and an optional target repo, then exposes the returned token as a step output (masked with core.setSecret). Callers must grant id-token: write on the job.

Reviewed by Cursor Bugbot for commit 8bfb594. Bugbot is set up for automated code reviews on this repo. Configure here.

@Mrtenz Mrtenz marked this pull request as ready for review May 28, 2026 20:28
Comment thread .github/actions/get-token/action.yml Outdated
},
body: JSON.stringify({
oidcToken: OIDC_TOKEN,
targetRepo: GITHUB_REPOSITORY,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add the repository as an input to this action in case a workflow needs access to a different one? For example, the extension has a workflow that pushes commits to the metamask-extension-ts-migration-dashboard repo (https://github.com/MetaMask/metamask-extension/blob/b393e3f210b55756508c507cbd0920e7951dc281/.github/workflows/build-ts-migration-dashboard.yml#L42). I believe it uses a metamaskbot token but we might eventually want to use this action.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, makes sense. I've added an option target-repository which defaults to github.repository (i.e., the current repository).

script: |
const token = await core.getIDToken('api://token-exchange-service');
core.setSecret(token);
core.setOutput('token', token);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why save the token to outputs in this step?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the OIDC token used to request the access token in the second step. It's referenced below:

OIDC_TOKEN: ${{ steps.oidc-token.outputs.token }}

In case you're confused about the core.setSecret above, that's actually just the add-mask workflow command and doesn't actually set a secret.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, thanks for the clarification.

Copy link
Copy Markdown
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@Mrtenz Mrtenz merged commit 9374835 into main May 29, 2026
10 checks passed
@Mrtenz Mrtenz deleted the mrtenz/get-token-action branch May 29, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants