The distribution of JavaScript libraries and tools through the Node Package Manager (npm) is a foundational element of modern web development. For years, the standard method for publishing packages involved storing personal authentication tokens within the secrets of a CI/CD pipeline, creating a persistent security risk where a compromised token could expose the entire publishing pipeline. The industry has shifted toward more robust, secure, and automated workflows, specifically leveraging GitHub Actions to handle the continuous deployment of npm packages. This evolution centers on two primary approaches: the legacy method using classic authentication tokens and the modern, more secure implementation using npm’s Trusted Publishers feature with OpenID Connect (OIDC). Understanding the nuances of configuration, security permissions, and workflow triggers is essential for developers aiming to establish a reliable, secure, and automated publishing infrastructure.
The Anatomy of an NPM Package Configuration
Before configuring the publishing pipeline, the package.json file must be meticulously structured to support both the distribution and the security features of the modern npm ecosystem. The name field requires a unique identifier on the npm registry, while the version field serves as the critical trigger for many automated publishing workflows. The main key defines the entry point of the module, typically pointing to a file such as index.js. Additionally, the files block dictates exactly which files are included in the published package, ensuring that only necessary code and assets are distributed.
The repository block within package.json plays a dual role. It is utilized by npm for provenance statements, a security feature that cryptographically signs the package to verify its origin. This is particularly relevant when using the npm publish --provenance option. For provenance to function correctly with Trusted Publishers, the repository.url field must exactly match the GitHub repository URL. This strict matching requirement prevents publishing errors that might arise from misconfigured packages or forks that have not updated their package.json to reflect the new repository location. While the repository block is not strictly required for basic publishing, it is a prerequisite for enabling provenance, which enhances trust in the package’s authenticity.
Legacy Publishing with Authentication Tokens
The traditional approach to publishing via GitHub Actions involves storing an npm authentication token as a repository secret. This method remains viable, particularly for developers who prefer granular control or are unable to use Trusted Publishers due to specific constraints, such as private repositories where provenance is not generated. To implement this, a developer must generate a "Classic Token" or an "Automation" token on the npm website. Automation tokens are specifically designed for this use case as they bypass two-factor authentication (2FA) requirements during the publishing process. It is important to note that the newer "Granular Access Token" format may present challenges, as it does not support tokens that never expire, potentially requiring manual secret updates in the future.
The workflow configuration for this method requires the NPM_TOKEN secret to be available in the environment during the publish step. The workflow typically triggers on a release event, specifically when a release is published. The actions/setup-node action is configured with the registry-url pointing to https://registry.npmjs.org. The npm publish command is then executed with the --provenance and --access public flags. The --provenance flag only works if the repository block is correctly configured in package.json. This method relies on the NODE_AUTH_TOKEN environment variable, which is mapped to the NPM_TOKEN secret in the GitHub repository settings.
Transitioning to Trusted Publishers and OIDC
npm’s Trusted Publishers feature represents a significant advancement in security, eliminating the need to store long-lived authentication tokens in CI/CD secrets. Instead, it utilizes OpenID Connect (OIDC) to authenticate the publishing workflow. This method generates short-lived tokens that are specific to the execution of the workflow, significantly reducing the attack surface. To configure Trusted Publishing, a developer must navigate to the npm website and select the CI/CD provider, in this case, GitHub Actions. The configuration requires entering the GitHub username or organization name, the repository name, and the exact name of the workflow file located in the .github/workflows directory. It is crucial to note that each package can only have one trusted publisher configured at a time.
The critical technical requirement for OIDC authentication in GitHub Actions is the id-token: write permission. This permission allows GitHub Actions to generate the OIDC token that npm validates. Without this permission, the authentication will fail. The workflow must also include contents: read to allow the checkout action to access the repository code. The actions/setup-node action is still used to configure the Node.js environment and the registry URL, but the NPM_TOKEN secret is no longer needed for the publish command itself. This shift not only enhances security but also simplifies token management, as there are no tokens to rotate or worry about expiring.
Workflow Configuration and Permissions
Configuring the GitHub Actions workflow for Trusted Publishing requires precise attention to detail. The workflow file, often named ci.yml or publish.yml, must define the permissions explicitly. The permissions block at the job level or the workflow level must include id-token: write and contents: read. The id-token: write permission is the linchpin of the OIDC process, enabling the workflow to prove its identity to the npm registry. The workflow typically triggers on push events, specifically targeting tags that match a pattern such as v*. This ensures that a new version is only published when a version tag is pushed to the repository, a common practice in semantic versioning.
The workflow steps involve checking out the code, setting up the Node.js environment with the specified version (e.g., Node 24), and then running the necessary build and test commands. The npm ci command is preferred for installing dependencies as it performs a clean install based on the lock file. After the build and test steps are successful, the npm publish command is executed. If the package has private dependencies, a read-only token is still required for the npm ci step to authenticate with the private registry, but the npm publish step itself uses the OIDC token generated by GitHub Actions. This separation of concerns enhances security by limiting the scope of the read-only token to dependency installation only.
yaml
name: Publish to npm
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- run: npm publish --ignore-scripts
Alternative Triggers and Third-Party Actions
While Trusted Publishing with OIDC is the recommended secure approach, there are alternative methods for triggering publishes. One popular option is the third-party GitHub Action npm-publish. This action automates the publishing process by detecting changes to the version field in package.json on the main branch. It is designed for continuous deployment scenarios where the version is incremented directly in the code. The action includes a smart check that compares the version in package.json with the latest version on the npm registry, ensuring that a publish only occurs if there is a version mismatch. This action keeps the npm authentication token secret and does not read or write to the ~/.npmrc file. However, if a developer prefers to publish on tags or uses an alternative package manager like pnpm, they may opt to configure setup-node with the registry-url option and call the package manager’s publish command directly. This manual approach is often considered more secure and customizable than relying on a third-party action.
For those who use other CI/CD providers, such as GitLab CI/CD, the concept of Trusted Publishing is also supported. In GitLab, the configuration involves defining id_tokens in the .gitlab-ci.yml file, specifying the audience for the npm registry. This allows GitLab to generate an OIDC token for npm, similar to the GitHub Actions workflow. The workflow typically runs on tags, ensuring that only tagged versions are published. This cross-platform support demonstrates the industry-wide shift toward OIDC-based authentication for package publishing.
Troubleshooting and Common Pitfalls
Despite the robustness of modern publishing workflows, several common issues can arise. One frequent error is the "Unable to authenticate" (ENEEDAUTH) error. This is often caused by a mismatch in the workflow filename configured on npmjs.com versus the actual filename in the repository. All fields, including the filename extension (.yml or .yaml), are case-sensitive and must match exactly. Another common pitfall is the misconfiguration of the repository.url in package.json. If this field does not exactly match the GitHub repository URL, provenance statements will fail, and in some cases, publishing may be blocked.
Self-hosted runners are currently not supported for Trusted Publishing. This is a known limitation, and support for self-hosted runners is planned for future releases. Until then, developers must use cloud-hosted runners such as GitHub-hosted runners, GitLab.com shared runners, or CircleCI cloud. Additionally, for packages in private repositories, provenance will not be generated even when using Trusted Publishing. This is a current limitation of the npm ecosystem. Developers using workflow_call to invoke other workflows or workflow_dispatch for manual publishing must ensure that the id-token: write permission is granted to both the parent and child workflows. Validation checks may inspect the calling workflow’s name rather than the workflow containing the publish command, leading to configuration mismatches if not carefully managed.
Security Best Practices and Token Management
Security remains the paramount concern in package publishing. Even when using Trusted Publishing, developers should adhere to best practices for handling dependencies. If a package includes private dependencies, a read-only token should be used for the npm ci step. This token should be granular and limited in scope to minimize the potential damage if it is compromised. The NODE_AUTH_TOKEN environment variable can be used to pass this read-only token to the installation step, while the publish step relies on the OIDC token. This separation ensures that the publishing capability is not exposed in the dependency installation phase.
For those still using classic tokens, it is crucial to generate tokens with the minimum necessary permissions. Automation tokens are preferred over user tokens as they are designed for machine-to-machine interactions and bypass 2FA. Developers should also consider implementing additional security practices, such as regularly rotating tokens and monitoring for unusual activity. The shift to OIDC-based Trusted Publishing significantly mitigates these risks by eliminating the need for long-lived secrets, but vigilance in configuration and dependency management remains essential.
Conclusion
The evolution of npm package publishing from static authentication tokens to dynamic, OIDC-based Trusted Publishers marks a significant leap in security and automation for the JavaScript ecosystem. By leveraging GitHub Actions with the id-token: write permission, developers can establish a robust, secure, and automated pipeline that eliminates the risks associated with long-lived secrets. The precise configuration of package.json, particularly the repository field for provenance, and the careful management of workflow permissions are critical to the success of these pipelines. While challenges such as private repository provenance limitations and self-hosted runner support remain, the current state of the art provides a secure and efficient method for distributing packages. As the ecosystem continues to mature, the adoption of these best practices will become standard, ensuring that the software supply chain remains resilient against emerging threats.