The npm-cli-login package exists to script the interactive npm login prompt for CI environments — and after npm's late-2025 authentication overhaul, you should not be using it, because username-and-password logins in automation are exactly the pattern the registry is engineering away. If you searched for npm login cli automation because a pipeline just broke, the good news is that every use case it served now has a first-class, safer replacement. This post covers what changed, why the old pattern was always risky, and the three patterns that work today.
Why npm login Never Fit CI
npm login is interactive by design: it prompts for username, password, and a second factor, then writes a credential to ~/.npmrc. CI jobs cannot answer prompts, so the ecosystem grew workarounds — npm-cli-login (an npm package that drives the prompts programmatically), expect scripts, and piped heredocs.
All of them shared the same flaw: they required storing a real npm password in CI secrets. A password is the master credential for the account — it can change email addresses, disable 2FA in some flows, create tokens, and publish anything. Any CI log leak, compromised runner, or malicious workflow step that read it owned the account outright. Token-based auth was always the better answer; the login-automation packages persisted mainly because old tutorials kept recommending them.
What Changed in Late 2025
The npm registry's response to 2025's supply chain attacks — most visibly the September phishing compromise of high-download maintainers and the Shai-Hulud self-replicating worm that spread via stolen publish credentials — was to shorten the lifetime of every credential class:
- Classic tokens are gone. Creation was disabled on November 5, 2025, and all existing classic tokens were permanently revoked on December 9, 2025. If a pipeline is failing with an auth error this quarter, this is the likely reason.
npm loginnow issues a two-hour session token. Interactive logins no longer mint long-lived credentials, which kills thenpm cli loginautomation pattern outright — even if you script the prompts, the resulting credential dies within two hours.- Granular access tokens got tighter defaults. New write-enabled tokens default to a 7-day lifetime with a hard maximum of 90 days, and enforce 2FA on publish by default (with an explicit bypass option intended for CI).
- Local publishes prompt for 2FA. Human publishes are interactive by default; automation is expected to use granular tokens or OIDC.
The direction is unambiguous: no more immortal secrets. Your CI auth strategy needs to assume rotation.
Pattern 1: Granular Token via Environment Variable
The workhorse pattern. Create a granular access token scoped to only the packages your pipeline publishes (or read-only, if the job just installs from a private registry), then reference it from an environment variable — never write the literal token to a file in the repo:
# .npmrc (committed — contains no secret)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
# GitHub Actions step
- run: npm publish --access public
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Rules that keep this pattern safe:
- Scope minimally. One token per pipeline, limited to the specific packages and permission (read vs. read-write) it needs. A read token for
npm cishould not be able to publish. - Treat the 90-day cap as a feature. Put token expiry on the calendar or, better, automate rotation. An expired-token build failure is annoying; it is also proof the blast radius of a leak is now bounded.
- Never echo
.npmrcin CI logs, and keep.npmrcfiles with real tokens out of Docker images — a token baked into an image layer survives image distribution. Use build secrets (docker build --secret) instead ofARG/ENV.
Pattern 2: Trusted Publishing (Kill the Token Entirely)
For publishing from GitHub Actions or GitLab CI/CD, OIDC trusted publishing is now the strongest option and the one npm itself recommends. The workflow authenticates with a short-lived, workflow-bound OIDC credential exchanged automatically at publish time. There is no stored secret to steal, rotate, or accidentally print:
permissions:
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
- run: npm publish --provenance --access public
Configure the trusted publisher once in the package settings on npmjs.com (repository, workflow filename, optional environment). Add --provenance while you are there — the signed build attestation is nearly free at this point. We walk through the full release setup in our guide to creating and publishing npm packages securely.
The main current limitation: OIDC support covers GitHub Actions and GitLab CI/CD, with other providers on npm's roadmap. Jenkins, Buildkite, and friends still need Pattern 1.
Pattern 3: Private Registries and Proxies
If your CI authenticates to Artifactory, Verdaccio, GitHub Packages, or another private registry, the same principles apply with registry-specific mechanics — scoped registry lines in .npmrc, short-lived credentials where the platform supports them:
@yourorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
GitHub Packages is the pleasant case: the ephemeral, automatically-issued GITHUB_TOKEN works for both installs and publishes within the same org, giving you rotation for free.
Auditing What You Have Now
Most teams accumulated npm credentials over years. A one-hour audit pays for itself:
# List tokens on your account (also manageable from npmjs.com settings)
npm token list
Hunt for stragglers in the usual places: CI secret stores, ~/.npmrc on shared build machines, Dockerfiles, shell scripts in ops repos, and .npmrc files accidentally committed with literal tokens (grep for _authToken=npm_). Revoke anything unscoped or unexplained. Compromised publish credentials are how registry worms propagate — every stale token is standing attack surface, which is why continuous monitoring of your dependency supply chain matters as much as your own publish hygiene; platforms like Safeguard flag when a package you depend on ships a suspicious release, whoever's token signed it.
Migration Cheat Sheet
| If you have... | Move to... |
|---|---|
npm-cli-login or scripted npm login | Granular token via ${NPM_TOKEN}, or OIDC |
| Classic token in CI secrets | Already revoked — granular token or OIDC |
| Granular token, no expiry plan | Calendar the 90-day rotation or automate it |
| GitHub Actions / GitLab publish job | Trusted publishing + --provenance |
| Password stored anywhere automated | Delete it, rotate the password, enable phishing-resistant 2FA |
FAQ
What was npm-cli-login for?
It programmatically answered the interactive npm login prompts so CI jobs could authenticate with a username and password. It dates from an era when some registries only supported password auth; against npmjs.com today it is obsolete, and the two-hour session tokens issued by modern npm login make the approach useless for pipelines anyway.
How do I do an npm login from the CLI in CI today?
You do not log in at all. Provide a granular access token through an environment variable referenced in .npmrc (_authToken=${NPM_TOKEN}), or configure OIDC trusted publishing so the CI platform proves its identity without any stored secret.
Why do write tokens expire after 90 days now?
npm capped granular write-token lifetimes (7-day default, 90-day maximum) in October 2025 after multiple incidents where long-lived stolen tokens were used to publish malware. Short lifetimes bound how long a leaked credential is useful.
Is trusted publishing worth it for a small internal package?
If it publishes from GitHub Actions or GitLab CI/CD, yes — setup is a one-time form on npmjs.com and it deletes an entire category of secret management. For other CI systems, a tightly scoped granular token with scheduled rotation is the current best practice.