Tired of rotating your npm publish token? Let GitHub Actions publish without one

npm write tokens now live 90 days at most, so a CI job that publishes with NPM_TOKEN breaks every quarter until someone rotates the secret. Trusted publishing removes the secret: the workflow trades its GitHub OIDC token for a short-lived publish credential.

First, the publish job needs permission to mint an OIDC token, and npm 11.5.1 or newer:

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
      - run: npm install -g npm@11   # Node 22 ships an older npm
      - run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}   # fallback, remove later

Keeping NODE_AUTH_TOKEN during the switch is safe: npm tries OIDC first and only falls back to the token when no trusted publisher matches.

Then tell the registry which workflow may publish each package. This takes npm 11.15.0 or newer and an interactive login with 2FA. A bypass-2FA automation token gets a 403 here.

NPM_CONFIG_BROWSER=false npx npm@latest login --auth-type=web
npx npm@latest trust github @your-scope/your-pkg \
  --repo your-org/your-repo --file release.yml --allow-publish --yes

NPM_CONFIG_BROWSER=false makes npm just print the login URL and poll. Without it, npm can stay stuck on its "Press ENTER to open in the browser" prompt even after you approve, and pressing ENTER does not release it. On the 2FA page, tick "don't challenge" so a loop over many packages does not prompt for each one; sleep a couple of seconds between calls to stay under rate limits.

A few things to know before you start:

  • --file is the workflow file name, not a path. If you rename the workflow, publishing breaks until you replace the trust entry.
  • A package can have only one trusted publisher, and it must already exist: publish a brand-new package's first version with a token, then add its trust entry. After that, new versions need nothing.

After the next release publishes cleanly through OIDC (public repos also get provenance, visible in npm view <pkg>@<ver> dist.attestations), delete the NODE_AUTH_TOKEN line and the repo secret, and revoke the token. From then on, nothing expires.

Comments

  1. Markdown is allowed. HTML tags allowed: <strong>, <em>, <blockquote>, <code>, <pre>, <a>.