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 .npmrc has no usable token)
npm error 404 (when an empty _authToken is sent and the registry rejects)
Even though every documented prerequisite is met:
id-token: write permission ✅
- npm CLI 11.5.1+ ✅ (verified 11.14.1)
ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN set ✅
- Trusted Publisher configured on the package (
@sofereditor/core in 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=false
The 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 --provenance
With 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-url and write .npmrc manually with only the registry line, OR
- Add the
_authToken-stripping step shown above with a one-line comment explaining why.
-
Extend Troubleshooting to call out the most common failure mode for users following the example unchanged:
If you see ENEEDAUTH or E404 even though Trusted Publisher is configured and id-token: write is granted, check $RUNNER_TEMP/.npmrc (or ~/.npmrc) for a //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} line written by actions/setup-node. When NODE_AUTH_TOKEN is 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 before npm publish or avoid setup-node's registry-url option.
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.
Summary
Following the official GitHub Actions example in Trusted publishing for npm packages verbatim — without a
NODE_AUTH_TOKENsecret — 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 ✅ACTIONS_ID_TOKEN_REQUEST_URL/ACTIONS_ID_TOKEN_REQUEST_TOKENset ✅@sofereditor/corein our case) ✅Root cause
actions/setup-node@v4withregistry-url: 'https://registry.npmjs.org'writes the following to the runner's~/.npmrc(actually$RUNNER_TEMP/.npmrcviaNPM_CONFIG_USERCONFIG):The literal
${NODE_AUTH_TOKEN}placeholder is expanded by npm CLI at publish time from the environment. When noNPM_TOKENsecret is configured (the intended state for Trusted Publishers),NODE_AUTH_TOKENis 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 →ENEEDAUTHorE404.Reproduction
With Trusted Publisher correctly set up for the package on
npmjs.comand noNPM_TOKENsecret, the publish fails — exactly as the docs would suggest it should succeed.Workaround that worked
Remove the
_authTokenline from.npmrcaftersetup-noderuns:After this, npm CLI sees no auth in
.npmrc, detectsACTIONS_ID_TOKEN_REQUEST_*env vars, callshttps://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.0published via this workflow.Suggested doc improvements
Two complementary changes (PR follows):
Update the GitHub Actions example workflow to either:
registry-urland write.npmrcmanually with only the registry line, OR_authToken-stripping step shown above with a one-line comment explaining why.Extend Troubleshooting to call out the most common failure mode for users following the example unchanged:
I'm happy to open a PR with both changes.
Suggested diff for the .mdx