A long-lived NPM_TOKEN secret is a standing liability: anyone with access to the repository’s secrets, or anyone who compromises the runner, can exfiltrate it and publish under your package’s identity indefinitely until it is manually rotated. Configure OIDC trusted publishing incorrectly, though, and the workflow fails outright rather than falling back silently:

npm error code E401
npm error Unable to authenticate, need: Bearer
npm error This package requires trusted publishing, but no matching configuration
npm error was found for repository "acme/sdk" and workflow "ci.yml"

This guide walks through registering a trusted publisher on npmjs.com and matching your GitHub Actions release workflow to it exactly, so the OIDC exchange succeeds without any stored token at all.


Root Cause

Trusted publishing works by having npm’s registry verify claims embedded in the OIDC token a CI job presents — specifically the repository, the workflow filename, and optionally the deployment environment — against a trusted-publisher record you register in advance on npmjs.com. If the token’s claims do not match that record exactly (a different workflow filename, a fork instead of the canonical repository, a missing environment), npm rejects the exchange. There is intentionally no partial match or fallback: a mismatch is treated identically to no configuration at all.

OIDC trusted publishing token exchange A four-stage flow: the GitHub Actions job requests an OIDC id-token, presents it to the npm registry, npm verifies the repository and workflow claims against the registered trusted publisher, and on a match issues a short-lived credential that authorises the publish. OIDC trusted publishing exchange Actions job requests id-token OIDC token repo + workflow claims npm verifies vs trusted publisher Publish short-lived credential

Minimal Reproduction

A package with trusted publishing configured for release.yml, but a publish attempt coming from a differently-named workflow, reproduces the failure:

# .github/workflows/ci.yml — NOT the file registered as trusted publisher
name: CI
on:
  push:
    tags: ['v*']

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci && npm run build
      - run: npm publish --provenance

If npmjs.com has release.yml registered as the trusted workflow but this file is named ci.yml, the OIDC claims will not match and the publish is rejected.


Step-by-Step Fix

Step 1 — Register the trusted publisher on npmjs.com

In the package’s Settings → Publishing access page on npmjs.com, add a trusted publisher entry specifying:

  • Provider: GitHub Actions
  • Repository: acme/sdk
  • Workflow filename: release.yml (must match the file path under .github/workflows/ exactly, filename only)
  • Environment (optional): leave blank unless the workflow uses a GitHub Environments deployment gate

Expected result: the package’s publishing-access page shows the trusted publisher entry as active, alongside any existing token-based access that has not yet been removed.

Step 2 — Name the workflow file to match the registration exactly

- .github/workflows/ci.yml
+ .github/workflows/release.yml
name: Release
on:
  push:
    tags: ['v*']

permissions:
  contents: read
  id-token: write

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci && npm run build
      - run: npm publish --provenance

Expected result: notice NODE_AUTH_TOKEN and any NPM_TOKEN reference are gone entirely — trusted publishing needs no stored secret once the claims match.

HAZARD PREVENTION

Symptom: The workflow filename matches, but the publish still fails with the same authentication error.

Root cause: The workflow runs from a fork, a different branch than expected, or a pull request context — trusted publisher matching is strict about the repository being the canonical one, not merely a fork with an identical filename.

Fix: Confirm the failing run’s “Actions” tab shows the canonical repository (acme/sdk, not contributor-fork/sdk) as the workflow owner, and that the trigger is a tag push, not a pull_request event.

Step 3 — Confirm the npm CLI version supports trusted publishing

- uses: actions/setup-node@v4
  with:
    node-version: 20
- run: npm install -g npm@latest   # ensure npm 11.5+ for trusted publishing
- run: npm --version

Expected result: npm --version reports 11.5.0 or later; older CLI versions do not attempt the OIDC exchange and fall back to requiring a token, producing a misleading authentication error instead of a clear trusted-publishing message.

Step 4 — Publish and confirm the OIDC exchange succeeded

npm publish --provenance

Expected output:

npm notice Publishing to https://registry.npmjs.org/ with tag latest and default access
npm notice using trusted publisher OIDC exchange (no token)
npm notice === Provenance Statement ===
npm notice sigstore verification successful
npm notice + @acme/[email protected]

Verification

# Confirm no token env var is referenced by the workflow at all
grep -r "NPM_TOKEN\|NODE_AUTH_TOKEN" .github/workflows/release.yml || echo "no token references — trusted publishing only"

# Confirm the published version's provenance links back to the exact workflow
npm view @acme/[email protected] dist.attestations.url

Expected result: the grep finds no matches, confirming the workflow relies solely on the OIDC exchange, and the attestation URL resolves to a Sigstore entry referencing acme/sdk and release.yml.


Edge Cases / Gotchas

  • Monorepos publishing multiple packages from one workflow need a trusted publisher entry per package, each pointing at the same workflow filename if they all publish from it.
  • Renaming the workflow file breaks trusted publishing immediately; update the npmjs.com registration in the same commit or pull request that renames the file, and expect one publish attempt to fail during the transition window if timing is off.
  • GitHub Environments add an extra claim (the environment name) to the OIDC token; if you registered an environment on npmjs.com but the workflow does not declare environment: on the job, the claims will not match.
  • Self-hosted GitHub Actions runners still work, since the OIDC token is issued by GitHub’s cloud-hosted identity provider regardless of where the job executes — only network egress to token.actions.githubusercontent.com and the npm registry matters.
  • Migrating from token auth should be done gradually: keep the NPM_TOKEN secret configured as a fallback for one release cycle while confirming trusted publishing succeeds, then remove the secret and any granular access token tied to CI.

Frequently Asked Questions

Do I still need an NPM_TOKEN secret once trusted publishing is configured?

No, for the workflow that matches the registered trusted publisher. Remove the NODE_AUTH_TOKEN environment variable and NPM_TOKEN secret from that job entirely — leaving a stale token in place is harmless but unnecessary, since npm CLI 11.5+ prefers the OIDC exchange automatically when trusted publishing is configured.

Can multiple workflows in the same repository publish the same package?

Only if each workflow file is individually registered as a trusted publisher for that package. npm matches on the exact workflow filename and repository, not just the repository alone, so a release.yml and a hotfix.yml would each need their own trusted publisher entry.

What happens if someone renames the workflow file?

Publishing breaks immediately, because npm’s trusted publisher check matches on the exact workflow filename recorded at registration time. Update the trusted publisher configuration on npmjs.com to reference the new filename before or immediately after renaming the workflow, or keep the old filename until the migration is complete.

Does trusted publishing work with GitLab CI or only GitHub Actions?

npm’s trusted publishing supports both GitHub Actions and GitLab CI as OIDC issuers, though the registration flow and matched claims differ slightly per provider. Check the trusted publisher configuration screen on npmjs.com for the current list of supported OIDC issuers before committing to one provider’s workflow syntax.

Is trusted publishing compatible with npm publish --provenance?

Yes, and they are commonly used together. Trusted publishing replaces the authentication credential; --provenance still generates the Sigstore attestation on top of that authenticated publish. Using both gives you a token-free publish that is also independently verifiable by consumers.



↑ Back to Automating npm Releases with GitHub Actions