Fix npm "access token expired or revoked" (E401) errors. Re-authenticate with npm login or replace the stale token in your .npmrc, and use OIDC trusted publishing in CI.
The "npm notice access token expired or revoked. Please try logging in again." message (an E401 from the registry) means npm sent an authentication token the registry no longer accepts. Tokens are credentials that prove your identity when installing private packages or publishing. npm tokens are not permanent. Granular access tokens have a configurable expiration that you set when you create them (with an upper limit of 90 days for the longest-lived option), and npm has been deprecating older "classic"/legacy tokens in favor of granular tokens and OIDC-based trusted publishing. A token can also stop working if it was manually revoked, if your account password changed, or if the registry administrator invalidated it. When npm sends an expired or revoked token, the registry replies with 401 Unauthorized and the operation stops. The token npm uses comes from an `.npmrc` file (user-level `~/.npmrc`, a project `.npmrc`, or one generated in CI from an environment variable). If that stored token is expired or invalid, every authenticated request fails until you replace it.
For local work, the quickest fix is to log in again, which writes a fresh token into your user-level ~/.npmrc:
npm loginYou'll be prompted for your username, password, email, and a one-time password if two-factor authentication is enabled (modern npm typically opens a browser to complete login). After it finishes, verify you're authenticated:
npm whoamiIf npm whoami prints your username, the new token is in place and installs/publishes should work again.
For automated workflows that still use a token (GitHub Actions, GitLab CI, Jenkins, etc.), create a new granular access token and store it as a CI secret rather than committing it.
Using the npm website (recommended, gives full control over scope and expiration):
1. Go to your account's Access Tokens page: https://www.npmjs.com/settings/<your-username>/tokens
2. Click Generate New Token and choose Granular Access Token.
3. Set an expiration date (you can choose up to 90 days for the longest-lived option).
4. Restrict the token to only the packages/scopes it needs, and grant the minimum permission (read-only for installs, read-and-write only for publishing).
5. Copy the token immediately — it is shown only once.
Store the value in your CI provider's secret store as NPM_TOKEN, then reference it in .npmrc via an environment variable so the secret is never written to disk in plaintext:
# .npmrc (committed; the token itself is NOT committed)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}Because granular tokens expire, set a reminder to rotate this before its expiration date.
A malformed or stale token line in .npmrc produces the same E401. Check the relevant files:
# User-level (most common)
cat ~/.npmrc
# Project-level (overrides user-level for this project)
cat .npmrcLook for an _authToken line and confirm the value is intact (not truncated) and points at the registry you actually use. Remove or replace any obviously stale token line. Editing the file is safer than deleting it, since ~/.npmrc often holds other settings you want to keep:
nano ~/.npmrc # remove or update the expired _authToken lineFor scoped packages, make sure both the registry mapping and its auth line are present:
@yourscope:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}A simple installed-state mismatch usually does not require wiping caches, but if installs still misbehave after fixing auth, reinstalling dependencies is a safe reset:
rm -rf node_modules
npm ci # or: npm installFor publishing from CI, trusted publishing lets the registry verify your workflow's OIDC identity, so there is no long-lived token to store or rotate. The key difference from token auth: you grant the job id-token: write permission and you do not set NODE_AUTH_TOKEN or an NPM_TOKEN secret — npm exchanges the OIDC identity for short-lived credentials automatically.
First, enable trusted publishing for the package:
1. Open the package's access settings: https://www.npmjs.com/package/<package-name>/access
2. Configure a trusted publisher for your CI provider (e.g. GitHub Actions), specifying the repository and workflow filename.
GitHub Actions example (no NPM_TOKEN):
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # required for OIDC trusted publishing
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
- run: npm publish
# No NODE_AUTH_TOKEN / NPM_TOKEN needed — npm uses the OIDC identityUse a recent npm CLI (trusted publishing requires up-to-date tooling; npm install -g npm@latest). Once trusted publishing works, delete any NPM_TOKEN secret from the workflow.
If the failing registry is not npmjs.org, renew the credential through that provider.
GitHub Packages — use a personal access token (or GITHUB_TOKEN in Actions) with the read:packages / write:packages scope, referenced via an environment variable:
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}Generate or rotate a PAT at https://github.com/settings/tokens.
Azure DevOps Artifacts — create a new Personal Access Token with the Packaging (Read & Write) scope, then update .npmrc. The _password value must be the base64-encoded PAT:
registry=https://pkgs.dev.azure.com/ORG/_packaging/FEED/npm/registry/
always-auth=true
//pkgs.dev.azure.com/ORG/_packaging/FEED/npm/registry/:username=anything
//pkgs.dev.azure.com/ORG/_packaging/FEED/npm/registry/:_password=[BASE64_ENCODED_PAT]
//pkgs.dev.azure.com/ORG/_packaging/FEED/npm/registry/:email=anythingJFrog Artifactory — re-authenticate to regenerate the npm auth config:
jf rt loginToken security best practices
- Least privilege: create read-only granular tokens for installing and read-and-write tokens only when publishing, scoped to the specific packages they need.
- Rotate before expiry: granular tokens expire on the date you set, so rotate proactively rather than waiting for a pipeline to break.
- Separate tokens per environment: distinct tokens for CI, local development, and individual team members make revocation and auditing far easier.
- Audit regularly: review and revoke unused tokens at https://www.npmjs.com/settings/~/tokens.
- Keep tokens out of committed files: a committed .npmrc should reference tokens via environment variables (${NPM_TOKEN}), never embed the literal secret. Keep personal tokens in user-level ~/.npmrc or in CI secret stores.
Why expiration exists: the npm registry has been targeted by supply-chain attacks where compromised accounts with long-lived credentials were used to publish malicious package versions. Expiring tokens and OIDC trusted publishing shrink the window an attacker can use a leaked credential, which is why npm has been deprecating long-lived classic tokens in favor of granular tokens and trusted publishing.
Migrating off classic/legacy tokens: if a workflow still relies on a legacy token that the registry now rejects, replace it with a granular access token (step 2) or, for publishing, switch to OIDC trusted publishing (step 4).
Debugging which registry is failing: run the failing command with verbose logging to confirm the exact registry URL and auth line in use:
npm install --loglevel=verboseLook for the registry host and authorization/401 lines to see whether the request is hitting the registry you configured and which credential it sent.
npm ERR! code EAI_AGAIN
How to fix "EAI_AGAIN" in npm
npm error code E403 npm error 403 Forbidden - PUT https://registry.npmjs.org/<package>
How to fix 'E403 Forbidden' error in npm
npm ERR! code EUSAGE npm ERR! Usage error
How to fix "npm ERR! code EUSAGE" in Node.js projects
npm ERR! code E401 npm ERR! 401 Unauthorized
How to fix "E401 Unauthorized" in npm
npm ERR! DEPTH_ZERO_SELF_SIGNED_CERT
How to fix "DEPTH_ZERO_SELF_SIGNED_CERT" in npm