Configuring OIDC Trusted Publishing for npm
Set up OIDC trusted publishing so GitHub Actions publishes to npm without a long-lived token, using short-lived id-token credentials and provenance.
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.
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, notcontributor-fork/sdk) as the workflow owner, and that the trigger is a tag push, not apull_requestevent.
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.comand the npm registry matters. - Migrating from token auth should be done gradually: keep the
NPM_TOKENsecret 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.
Related
- Automating npm Releases with GitHub Actions — the full release workflow structure that this trusted-publisher configuration replaces the token step in.
- npm Provenance & Sigstore Attestation — the attestation mechanism that pairs naturally with a trusted, token-free publish.
- semantic-release for Dual-Format Packages — automating the version and tag step that triggers this same trusted-publishing workflow.