⚠️ URGENT ACTION REQUIRED

npm classic tokens will be REVOKED on November 19, 2025 (7 days from now)

All automated publishing workflows using classic tokens will FAIL after this date. Migration is mandatory.

URGENT SECURITY npm

🚨 npm Classic Tokens Being Revoked November 19: Complete Migration Guide

Dillip Chowdary
Dillip Chowdary
November 12, 2025 • 12 min read

What's Happening

npm has disabled the creation of classic tokens and will revoke ALL existing classic tokens on November 19, 2025. This is a mandatory security upgrade affecting millions of developers and automated workflows.

Why This Change?

  • Security: Classic tokens had overly broad permissions with no scoping capabilities
  • Granularity: New tokens support package-specific and permission-specific access
  • Auditability: Granular tokens provide better audit trails and can be rotated per-package
  • Provenance: Trusted publishing enables cryptographic proof of package origin

⚠️ Impact on Your Workflows

If you use classic tokens in:

  • GitHub Actions workflows (.github/workflows/*.yml)
  • GitLab CI/CD pipelines (.gitlab-ci.yml)
  • CircleCI, Travis CI, Jenkins, or other CI systems
  • Local .npmrc files
  • Docker build processes
  • Automated deployment scripts

All these workflows will FAIL after November 19 unless you migrate.

Timeline & Deadlines

October 2025

npm disabled creation of new classic tokens

!
November 12, 2025 (TODAY)

7 days remaining to migrate

November 19, 2025

ALL classic tokens will be revoked

×
November 20, 2025+

Classic tokens no longer function. Workflows using them will fail.

Check If You're Affected

1Check npm Access Tokens

Log in to npm and visit your access tokens page:

# Visit: https://www.npmjs.com/settings/YOUR_USERNAME/tokens

# Or use npm CLI to list tokens
npm token list

Look for tokens marked as "Legacy Token" or "Read and Publish" (legacy format).

2Check CI/CD Secrets

Search your repositories for npm token usage:

# Search for NPM_TOKEN in GitHub Actions
grep -r "NPM_TOKEN" .github/workflows/

# Search in package.json scripts
grep -r "NPM_TOKEN" package.json

# Search in .npmrc files
find . -name ".npmrc" -exec grep "authToken" {} \;

3Check Local Configuration

# Check global .npmrc
cat ~/.npmrc

# Check project .npmrc
cat .npmrc

# Look for lines like:
# //registry.npmjs.org/:_authToken=npm_XXXXXXXXXXXX

Migration Options

Option 1: Granular Access Tokens

Fine-grained tokens with package-specific permissions

  • Package-specific scoping
  • Read-only, publish, or admin permissions
  • Works with all CI/CD systems
  • Easy to rotate per package
BEST FOR:
Multi-package monorepos, legacy CI systems, local development

Option 2: Trusted Publishing

Zero-token authentication with provenance

  • No tokens to manage or rotate
  • Cryptographic proof of origin
  • Automatic provenance attestations
  • GitHub Actions only (for now)
BEST FOR:
GitHub-hosted projects, security-focused teams, new projects

Recommendation

If you use GitHub Actions, migrate to Trusted Publishing for zero-token security. For all other CI systems or multi-platform needs, use Granular Access Tokens.

Granular Access Tokens Setup

1Create a Granular Access Token

  1. 1. Go to npm Access Tokens:
    https://www.npmjs.com/settings/YOUR_USERNAME/tokens
  2. 2. Click "Generate New Token" → "Granular Access Token"
  3. 3. Configure Token Settings:
    • Token Name: my-package-publish-token
    • Expiration: Custom (e.g., 1 year) or No expiration
    • Packages and scopes: Select specific packages
    • Organizations: Select if applicable
    • Permissions: Choose "Read and write"

2Add Token to CI/CD Secrets

GitHub Actions:

# Go to: Repository Settings → Secrets and variables → Actions
# Click "New repository secret"
# Name: NPM_TOKEN
# Value: npm_xxxxxxxxxxxxxxxxxxxx (paste your granular token)

GitLab CI:

# Go to: Project Settings → CI/CD → Variables
# Click "Add variable"
# Key: NPM_TOKEN
# Value: npm_xxxxxxxxxxxxxxxxxxxx
# ✓ Mask variable
# ✓ Protect variable (optional)

3Update CI/CD Configuration

Your existing CI/CD workflows should continue working with granular tokens using the same NPM_TOKEN environment variable:

# .github/workflows/publish.yml
name: Publish to npm

on:
  release:
    types: [created]

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 test
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Migration Complete!

Simply replace your classic token secret with the new granular token. No code changes needed!

Trusted Publishing (Provenance)

Trusted publishing eliminates tokens entirely by using OpenID Connect (OIDC) to authenticate GitHub Actions directly with npm. This provides cryptographic proof of where your package came from.

Benefits of Trusted Publishing

  • Zero secrets - No NPM_TOKEN to manage or rotate
  • Automatic provenance - Cryptographic attestations added to every publish
  • Supply chain security - Users can verify package origin
  • No token leaks - Nothing to accidentally commit or expose

1Enable Trusted Publishing on npm

  1. 1. Go to your package settings on npm:
    https://www.npmjs.com/package/YOUR_PACKAGE_NAME/access
  2. 2. Scroll to "Publishing access" section
  3. 3. Click "Add trusted publisher"
  4. 4. Configure GitHub Actions connection:
    • Repository owner: your-github-username
    • Repository name: your-repo-name
    • Workflow name: publish.yml (optional - restricts to specific workflow)
    • Environment: production (optional - restricts to specific environment)

2Update GitHub Actions Workflow

# .github/workflows/publish.yml
name: Publish to npm with Provenance

on:
  release:
    types: [created]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write  # REQUIRED for provenance
    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 test

      # Publish with provenance - NO TOKEN NEEDED!
      - run: npm publish --provenance --access public
        # NO env: NODE_AUTH_TOKEN needed!
        # GitHub OIDC handles authentication automatically

⚠️ Important: Remove NPM_TOKEN Secret

After migrating to trusted publishing, you can safely delete the NPM_TOKEN secret from your repository. The workflow no longer needs it!

3Verify Provenance

After publishing with provenance, users can verify your package origin:

# Check if package has provenance
npm view your-package-name dist.integrity

# Audit package provenance
npm audit signatures

Packages with provenance show a verified badge on npm:

✓ Provenance
Published from github.com/username/repo

CI/CD Configuration Examples

GitLab CI

# .gitlab-ci.yml
publish:
  stage: deploy
  image: node:20
  only:
    - tags
  script:
    - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
    - npm ci
    - npm test
    - npm publish
  variables:
    NPM_TOKEN: $NPM_TOKEN  # Set in GitLab CI/CD Variables

CircleCI

# .circleci/config.yml
version: 2.1

jobs:
  publish:
    docker:
      - image: cimg/node:20.10
    steps:
      - checkout
      - run:
          name: Authenticate with npm
          command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
      - run: npm ci
      - run: npm test
      - run: npm publish

workflows:
  publish-on-tag:
    jobs:
      - publish:
          filters:
            tags:
              only: /^v.*/
            branches:
              ignore: /.*/

Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent any

    environment {
        NPM_TOKEN = credentials('npm-token-credential-id')
    }

    stages {
        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
        }

        stage('Publish') {
            when {
                tag "v*"
            }
            steps {
                sh '''
                    echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
                    npm publish
                '''
            }
        }
    }
}

Travis CI

# .travis.yml
language: node_js
node_js:
  - '20'

deploy:
  provider: npm
  email: your-email@example.com
  api_token: $NPM_TOKEN
  on:
    tags: true

before_deploy:
  - npm ci
  - npm test

Azure Pipelines

# azure-pipelines.yml
trigger:
  tags:
    include:
      - v*

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'
    displayName: 'Install Node.js'

  - script: |
      npm ci
      npm test
    displayName: 'Install and Test'

  - script: |
      echo "//registry.npmjs.org/:_authToken=$(NPM_TOKEN)" > .npmrc
      npm publish
    displayName: 'Publish to npm'
    env:
      NPM_TOKEN: $(NPM_TOKEN)

Troubleshooting

Error: "You must be logged in to publish packages"

Cause: Token not properly configured in .npmrc or environment

Solution:

# Ensure .npmrc exists with:
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc

# Or set NODE_AUTH_TOKEN environment variable
export NODE_AUTH_TOKEN=$NPM_TOKEN

Error: "403 Forbidden - you do not have permission to publish"

Cause: Granular token doesn't have write access to the package

Solution:

  • Go to npm token settings and edit the token
  • Ensure the package is included in "Packages and scopes"
  • Ensure permission is set to "Read and write"

Error: "npm publish --provenance failed"

Cause: Missing id-token: write permission in workflow

Solution:

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write  # ADD THIS LINE
    steps:
      # ...

Multiple packages in monorepo

Solution: Create one granular token per package, or use organization-scoped tokens:

# Create tokens with package-specific access
# Token 1: @myorg/package-a (Read and write)
# Token 2: @myorg/package-b (Read and write)

# Or create one org-scoped token with write access to all packages
# Packages and scopes: @myorg/* (All packages)

Quick Reference

Granular Token Checklist

  • ☐ Create granular access token on npm
  • ☐ Add NPM_TOKEN to CI/CD secrets
  • ☐ Verify token has write permissions for your packages
  • ☐ Test publish in CI/CD
  • ☐ Delete old classic token

Trusted Publishing Checklist

  • ☐ Configure trusted publisher on npm package settings
  • ☐ Add id-token: write permission to workflow
  • ☐ Add --provenance flag to npm publish
  • ☐ Remove NPM_TOKEN from workflow
  • ☐ Delete NPM_TOKEN secret from repository
  • ☐ Test publish and verify provenance badge

Conclusion

The npm classic token deprecation is a mandatory security upgrade that affects the entire JavaScript ecosystem. With just 7 days until the November 19 deadline, immediate action is required.

✅ Next Steps

  1. 1. TODAY: Audit all repositories and CI/CD pipelines for classic token usage
  2. 2. THIS WEEK: Migrate to granular tokens or trusted publishing
  3. 3. BEFORE NOV 19: Test all automated workflows
  4. 4. NOV 19+: Delete classic tokens (npm will do this automatically)

For most teams using GitHub Actions, trusted publishing with provenance is the recommended path forward. It eliminates token management entirely while providing supply chain security benefits.

Additional Resources

Share this guide