cli icon indicating copy to clipboard operation
cli copied to clipboard

feat: adds support for oidc publish

Open reggi opened this issue 6 months ago • 5 comments

🎉 Introducing OIDC Support for npm Publishing!

Further discussions not related directly this PR should happen here https://github.com/orgs/community/discussions/161015

We're thrilled to announce a new security feature that makes publishing npm packages from CI environments easier and more secure! This PR adds OpenID Connect (OIDC) token support for npm publishing, which eliminates the need to store long-lived access tokens in CI secrets.

With OIDC support, you can now publish packages from GitHub Actions and GitLab CI with improved security through short-lived, automatically generated tokens. This is a major step forward in securing the npm ecosystem and simplifying CI/CD workflows.

Technical Details

This implementation adds OIDC token support by:

  1. Detecting when npm is running in a supported CI environment (currently GitHub Actions and GitLab CI)
  2. Retrieving an OIDC token from the CI provider
  3. Exchanging this token with the npm registry for a short-lived publishing token
  4. Using this token for authentication during the publish process

The feature is designed to be non-invasive - it only activates in CI environments and gracefully falls back to traditional authentication methods when OIDC isn't available.

For Publishers

Updating Package Settings on npmjs.com

[!WARNING]
Not live yet, OIDC support is currently under development. The CLI, npmjs.com, and registry changes will be rolled out incrementally. Stay tuned for a public preview announcement. As of now, this documentation reflects features planned for a future release.

[!IMPORTANT]
In order to use OIDC publishing, a package must already exist on npmjs.com. This means the initial publish needs to be done through conventional means; further publishes, once configured, can use OIDC.

Before using OIDC for publishing, ensure your package settings on npmjs.com are configured to allow CI/CD workflows.

  1. Log in to your npm account and navigate to the Package Settings page for the package you want to publish.
  2. Add a new Connection for a Trusted Publisher and fill out the details.
  3. Save your changes to apply the new settings.

This step ensures that your package is ready to accept tokens generated via OIDC workflows.

GitHub Actions

To publish with OIDC from GitHub Actions:

  1. Add the id-token: write permission to your workflow:
permissions:
  id-token: write
  contents: read
  1. No need to set up NPM_TOKEN secrets anymore! Just run npm publish as usual:
- name: Publish to npm
  run: npm publish

GitLab CI

To publish with OIDC from GitLab CI:

  1. Configure your GitLab CI pipeline to provide the ID token via the NPM_ID_TOKEN environment variable:
publish:
  script:
    - npm publish
  id_tokens:
    NPM_ID_TOKEN:
      aud: npm:registry.npmjs.org

For other CLI's

If you're building a CLI tool that publishes to npm registries, you can implement OIDC support by:

  1. Detecting CI environments
  2. For GitHub Actions, requesting tokens via the ACTIONS_ID_TOKEN_REQUEST_URL endpoint with the proper audience format (npm:registry.hostname)
  3. For GitLab CI, using the NPM_ID_TOKEN environment variable if available
  4. Exchanging the OIDC token with the npm registry's token exchange endpoint

For other Registries

As a registry, you'll need a way for package publishers to create connections between OIDC Trusted Publishers and the registry, similar to how we allow connections to be added on the package settings page of npmjs.com.

To support OIDC token authentication in your npm-compatible registry:

  1. Implement an endpoint at /-/npm/v1/oidc/token/exchange that accepts POST requests with:

    {
      "package_name": "package-name",
      "id_token": "jwt-token-from-ci-provider"
    }
    
  2. Verify the OIDC token using standard JWT validation practices, checking the audience claim matches your expected format (npm:your.registry.hostname)

  3. Return a response with a short-lived npm token:

    {
      "token": "npm_short_lived_token"
    }
    

Technical Overview

  • The OIDC integration begins in the publish command.
  • This adds a new utility module oidc.js handles:
  • Full test coverage has been implemented in the publish test suite. ✅
  • The authentication flow follows this path:
    • publish command → libnpmpublish module → npm-registry-fetch

Key touchpoints:

  • https://github.com/npm/cli/blob/latest/lib/commands/publish.js#L184
  • https://github.com/npm/cli/blob/latest/workspaces/libnpmpublish/lib/publish.js#L53-L63
  • https://github.com/npm/npm-registry-fetch/blob/main/lib/index.js#L55

This initial implementation is focused on the publish workflow only. Currently OIDC token support is limited to the publish command.

reggi avatar May 29 '25 18:05 reggi

  1. Is there a config option or command line argument that can prevent OIDC from kicking in? (it seems like "write" is a magic id-token string for GHA, so i'm not sure what i'd put there or in an env var to guarantee that it can't kick in)
  2. if i wanted to publish using OIDC on my local machine, for testing or for funsies, what would I need to set up and how would I need to invoke it?

ljharb avatar May 29 '25 18:05 ljharb

@ljharb 👋

Is there a config option or command line argument that can prevent OIDC from kicking in? (It seems like "write" is a magic id-token string for GHA, so I'm not sure what I'd put there or in an env var to guarantee that it can't kick in.)

I've thought about this a lot. I also want this, but I question its necessity. If you have id: write and don't configure the trusted connection on npmjs.com, the token-exchange endpoint will return a 404 and you won't get a token, and the publish flow will continue as normal. So, you're already opted out by not configuring it—though, yes, there is still an extra network request.

If I wanted to publish using OIDC on my local machine, for testing or for fun, what would I need to set up and how would I need to invoke it?

Not really possible. The whole point of OIDC is to have a trusted publisher (like GitLab or GitHub) as the issuer of a token that the registry trusts. A local machine isn't a trusted issuer, so we wouldn't be able to validate any token you could provide.

reggi avatar May 29 '25 18:05 reggi

Right, but what if an attacker configures OIDC on npmjs.com unbeknownst to me? I'd still want to ensure CI can't publish with it.

So to clarify, the reason it's not possible is because the npm servers only have a finite hardcoded list of "trusted OIDC publishers", and i'm not on it?

ljharb avatar May 29 '25 19:05 ljharb

@ljharb

Right, but what if an attacker configures OIDC on npmjs.com unbeknownst to me? I'd still want to ensure CI can't publish with it.

If an attacker has access to your npmjs.com account, they can just make gat tokens, I think OIDC would be the least of your worries 🤔 . Further expanding on this idea if a malicious actor got access to your account and added a trusted connection to one of THEIR repos, with their own workflow a --no-oidc flag isn't gonna help you at that point, they write the workflow, and the npm publish script in the workflow, they can just omit that flag.

So to clarify, the reason it's not possible is because the npm servers only have a finite hardcoded list of "trusted OIDC publishers", and i'm not on it?

Yeah, kinda

reggi avatar May 30 '25 17:05 reggi

Just to chime in, I'm onboard and supportive w/ the decision to avoid a cli parameter here for the sake of feature streamlining. Security wise, the malicious access goes similarly to creating access tokens, as mentioned above.

So to clarify, the reason it's not possible is because the npm servers only have a finite hardcoded list of "trusted OIDC publishers", and i'm not on it?

By finite we are kick starting this feature w/ 2 trusted publishers: GitHub Actions and GitLab.

This is an MVP and we want to understand the usage feedback for the next iterations on this feature.


If possible, I'd really love if we can shift other feature requests and feedback at the community discussion post as it makes it easier for me to track feedback received and we keep this PR limited to new changes added to the repo.

leobalter avatar May 30 '25 19:05 leobalter

I feel like this logic should not be in the npm CLI, but rather a part of setup-node or some other third party lib. Otherwise, this code will end up continuing to include more and more special cases for more CI providers, and have to be reimplemented by every other package manager or tool that needs the npm token directly (like the DefinitelyTyped infrastructure that publishes directly via the API, which I would love to use this with).

jakebailey avatar Jul 01 '25 17:07 jakebailey