Repository navigation
Description
Summary
Following the official GitHub Actions example in Trusted publishing for npm packages verbatim — without a NODE_AUTH_TOKEN secret — npm CLI never triggers the OIDC flow and instead fails with one of:
npm error code ENEEDAUTH(the.npmrchas no usable token)npm error 404(when an empty_authTokenis sent and the registry rejects)
Even though every documented prerequisite is met:
id-token: writepermission ✅- npm CLI 11.5.1+ ✅ (verified 11.14.1)
ACTIONS_ID_TOKEN_REQUEST_URL/ACTIONS_ID_TOKEN_REQUEST_TOKENset ✅- Trusted Publisher configured on the package (
@sofereditor/corein our case) ✅
Root cause
actions/setup-node@v4 with registry-url: 'https://registry.npmjs.org' writes the following to the runner's ~/.npmrc (actually $RUNNER_TEMP/.npmrc via NPM_CONFIG_USERCONFIG):
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
registry=https://registry.npmjs.org/
always-auth=falseThe literal ${NODE_AUTH_TOKEN} placeholder is expanded by npm CLI at publish time from the environment. When no NPM_TOKEN secret is configured (the intended state for Trusted Publishers), NODE_AUTH_TOKEN is unset and the substitution becomes an empty string. npm CLI sees the _authToken= line as "auth is configured" and does NOT initiate the OIDC token exchange, instead attempting a classic publish with empty credentials → ENEEDAUTH or E404.
Reproduction
name: Publish
on: workflow_dispatch
permissions:
id-token: write
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@latest --force # ensure 11.5.1+
- run: npm publish --access public --provenanceWith Trusted Publisher correctly set up for the package on npmjs.com and no NPM_TOKEN secret, the publish fails — exactly as the docs would suggest it should succeed.
Workaround that worked
Remove the _authToken line from .npmrc after setup-node runs:
- name: Strip _authToken from npmrc (let OIDC kick in)
run: |
npmrc="${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}"
sed -i '/_authToken/d' "$npmrc"After this, npm CLI sees no auth in .npmrc, detects ACTIONS_ID_TOKEN_REQUEST_* env vars, calls https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/<pkg> and publishes successfully with provenance.
Verified end-to-end: @sofereditor/{core,react,collab,export-pdf,export-docx,import-docx}@0.2.0 published via this workflow.
Suggested doc improvements
Two complementary changes (PR follows):
-
Update the GitHub Actions example workflow to either:
- Drop
registry-urland write.npmrcmanually with only the registry line, OR - Add the
_authToken-stripping step shown above with a one-line comment explaining why.
- Drop
-
Extend Troubleshooting to call out the most common failure mode for users following the example unchanged:
If you see
ENEEDAUTHorE404even though Trusted Publisher is configured andid-token: writeis granted, check$RUNNER_TEMP/.npmrc(or~/.npmrc) for a//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}line written byactions/setup-node. WhenNODE_AUTH_TOKENis unset (the expected state for Trusted Publishers without classic tokens), the placeholder expands to empty and npm CLI tries classic auth instead of OIDC. Either remove that line beforenpm publishor avoidsetup-node'sregistry-urloption.
I'm happy to open a PR with both changes.
Suggested diff for the .mdx
--- a/content/packages-and-modules/securing-your-code/trusted-publishers.mdx
+++ b/content/packages-and-modules/securing-your-code/trusted-publishers.mdx
@@ ... GitHub Actions example
- uses: actions/setup-node@v6
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false # never use caching in release builds
+ # `setup-node` with `registry-url` writes an `_authToken` line in
+ # `.npmrc` that expands to empty when `NODE_AUTH_TOKEN` is unset.
+ # Empty auth tokens prevent npm CLI from initiating OIDC. Strip
+ # the line so the OIDC flow kicks in.
+ - name: Strip empty _authToken from .npmrc
+ run: |
+ npmrc="${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}"
+ sed -i '/_authToken/d' "$npmrc"
- run: npm ci
- run: npm run build --if-present
- run: npm test
- run: npm publish
@@ ... Troubleshooting
If you encounter an "Unable to authenticate" (ENEEDAUTH) error when publishing, first verify that the workflow filename matches exactly what you configured on [npmjs.com](https://npmjs.com), including the `.yml` extension. ...
+
+If `ENEEDAUTH` or `E404` persist after you've verified the trusted publisher
+configuration, check the generated `.npmrc`. `actions/setup-node` with
+`registry-url` writes a `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}`
+line; without a `NODE_AUTH_TOKEN` secret the placeholder expands to empty
+and npm CLI treats that as "auth configured", short-circuiting the OIDC
+exchange. Either remove that line before `npm publish` or skip
+`registry-url` and configure the registry manually.
Activity
fix: repository.url format and remove registry-url from setup-node
fix: repository.url format and remove registry-url for OIDC (#173)
fix: OIDC — NODE_AUTH_TOKEN: '' forces OIDC fallback (npm/documentati…
chore(ci): publish nodeup-cli to npm via OIDC Trusted Publishing (#39)
ci(release): drop setup-node registry-url for OIDC publish (#228)
fix(0.6.4): OIDC trusted publishing — drop registry-url from setup-node
fix: unblock npm trusted-publishing OIDC exchange (1.15.4)
fix: unblock npm trusted-publishing OIDC exchange (1.15.4) (#47)
Drop registry-url from setup-node so OIDC actually engages
ci: publish via trusted publishing (OIDC), niente token
ci(sdk): configure npm publishing via GitHub Actions OIDC
feat(publish): BROKEN verdict for the setup-node OIDC breakage
CI: fix OIDC publish (drop registry-url), bump to 0.5.2
CI: make OIDC actually engage (registry-url + strip placeholder token…
Let the release workflow reach npm over OIDC [VER-375] (#49)
39 remaining items
ci: drop registry-url from the publish job so trusted publishing is n…
chore: release 0.2.0 with OIDC-correct release workflow
ci: make the OIDC publish path actually work
ci(release): strip empty _authToken from .npmrc to enable OIDC exchange
fix(ci): scrub auth lines and publish in one step for OIDC (#25)
fix(ci): drop setup-node registry-url so OIDC exchange runs
fix(ci): drop setup-node registry-url so OIDC exchange runs (#27)
fix(ci): restore registry-url + strip _authToken for OIDC publish (#28)
ci(release): drop NPM_TOKEN, publish via OIDC only (#553) (#553)
ci(npm): publish through trusted publishing, with no stored token (#345)
ci(sdk): configure npm publishing via GitHub Actions OIDC
ci: publish to npm and cut Releases automatically on version bumps
feat: publish as @kagurazakanyaa/dsh-web-login via npm Trusted Publis…
fix(ci): drop setup-node registry-url so npm uses OIDC
fix(publish): drop setup-node registry-url so npm uses OIDC