|
| 1 | +# How to set up npm authentication |
| 2 | + |
| 3 | +This is a brief guide on setting up npm authentication in GitHub Actions. Most of the information below are also applicable outside of Changesets and can be referenced for other npm workflows. |
| 4 | + |
| 5 | +## Recommended Setup |
| 6 | + |
| 7 | +It is recommended by npm to use [Trusted Publishing](https://docs.npmjs.com/trusted-publishers), or [Staged Publishing](https://docs.npmjs.com/staged-publishing), or both, to securely publish packages from CI. |
| 8 | + |
| 9 | +Note that Staged Publishing does not work with Changesets at the moment, so it's recommended to use Trusted Publishing instead for now. Check out [its docs](https://docs.npmjs.com/trusted-publishers) for more information to set it up. |
| 10 | + |
| 11 | +Also, in contrary to npm's [workflow recommendation](https://docs.npmjs.com/trusted-publishers#step-2-configure-your-cicd-workflow), make sure the `id-token: write` is only set on the job that needs to publish. As such, consider splitting the build, test, publish flows etc into separate jobs. Here's an example setup with Changesets: |
| 12 | + |
| 13 | +```yaml |
| 14 | +# .github/workflows/publish.yml |
| 15 | +name: Publish |
| 16 | + |
| 17 | +on: |
| 18 | + push: |
| 19 | + branches: |
| 20 | + - main |
| 21 | + |
| 22 | +permissions: {} # recommended: reset permissions |
| 23 | + |
| 24 | +jobs: |
| 25 | + select-mode: |
| 26 | + runs-on: ubuntu-latest |
| 27 | + outputs: |
| 28 | + mode: ${{ steps.select-mode.outputs.mode }} |
| 29 | + publish-plan-artifact-id: ${{ steps.select-mode.outputs.publish-plan-artifact-id }} |
| 30 | + permissions: |
| 31 | + contents: read # to check out repo (actions/checkout) |
| 32 | + steps: |
| 33 | + - uses: actions/checkout@v7 |
| 34 | + - run: npm install |
| 35 | + - uses: changesets/action/select-mode@v2 |
| 36 | + id: select-mode |
| 37 | + |
| 38 | + version: |
| 39 | + if: needs.select-mode.outputs.mode == 'version' |
| 40 | + needs: select-mode |
| 41 | + runs-on: ubuntu-latest |
| 42 | + outputs: |
| 43 | + version-dir-artifact-id: ${{ steps.version.outputs.version-dir-artifact-id }} |
| 44 | + permissions: |
| 45 | + contents: read # to check out repo (actions/checkout) |
| 46 | + steps: |
| 47 | + - uses: actions/checkout@v7 |
| 48 | + - run: npm install |
| 49 | + - uses: changesets/action/version@v2 |
| 50 | + id: version |
| 51 | + |
| 52 | + pack: |
| 53 | + if: needs.select-mode.outputs.mode == 'publish' |
| 54 | + needs: select-mode |
| 55 | + runs-on: ubuntu-latest |
| 56 | + outputs: |
| 57 | + pack-dir-artifact-id: ${{ steps.pack.outputs.pack-dir-artifact-id }} |
| 58 | + permissions: |
| 59 | + contents: read # to check out repo (actions/checkout) |
| 60 | + steps: |
| 61 | + - uses: actions/checkout@v7 |
| 62 | + - run: npm install |
| 63 | + - run: npm build |
| 64 | + - uses: changesets/action/pack@v2 |
| 65 | + id: pack |
| 66 | + with: |
| 67 | + publish-plan-artifact-id: ${{ needs.select-mode.outputs.publish-plan-artifact-id }} |
| 68 | + |
| 69 | + publish: |
| 70 | + needs: pack |
| 71 | + runs-on: ubuntu-latest |
| 72 | + permissions: |
| 73 | + id-token: write # for trusted publishing (changesets/action) |
| 74 | + steps: |
| 75 | + - uses: changesets/action/publish@v2 |
| 76 | + with: |
| 77 | + pack-dir-artifact-id: ${{ needs.pack.outputs.pack-dir-artifact-id }} |
| 78 | +``` |
| 79 | +
|
| 80 | +## Token-based Publishing |
| 81 | +
|
| 82 | +> [!CAUTION] |
| 83 | +> Token-based publishing (with [Granular Access Tokens](https://docs.npmjs.com/about-access-tokens#about-granular-access-tokens)) is no longer recommended, with many restrictions that make it difficult to use in CI workflows. For example: |
| 84 | +> |
| 85 | +> - They expire after a maximum of 90 days, which requires periodic manual token rotation. |
| 86 | +> - 2FA-bypass tokens are [being deprecated](https://github.blog/changelog/2026-07-08-npm-install-time-security-and-gat-bypass2fa-deprecation/#2fa-bypass-tokens-will-no-longer-publish-directly) and will soon be not allowed to publish packages with 2FA enabled. |
| 87 | +> |
| 88 | +> However, if you're using a different npm-compatible registry that does not support Trusted Publishing or Staged Publishing, you may still opt for token-based publishing. Check out the [next section](#token-based-publishing) for more information. |
| 89 | +
|
| 90 | +You'll need an [npm token](https://docs.npmjs.com/creating-and-viewing-authentication-tokens) with "Bypass two-factor authentication" checked (to prevent npm requesting 2FA in CI). [Add this token as a secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets) in your GitHub repo with the name `NPM_TOKEN` so it can be used in the workflow below. |
| 91 | + |
| 92 | +In most cases, you can use [actions/setup-node](https://github.com/actions/setup-node) to set up token-based authentication automatically. |
| 93 | + |
| 94 | +```yaml |
| 95 | +# .github/workflows/publish.yml |
| 96 | +name: Publish |
| 97 | +
|
| 98 | +on: |
| 99 | + push: |
| 100 | + branches: |
| 101 | + - main |
| 102 | +
|
| 103 | +permissions: {} # recommended: reset permissions |
| 104 | +
|
| 105 | +jobs: |
| 106 | + # ... other jobs like select-mode and version |
| 107 | + publish: |
| 108 | + if: needs.select-mode.outputs.mode == 'publish' |
| 109 | + needs: select-mode |
| 110 | + runs-on: ubuntu-latest |
| 111 | + permissions: |
| 112 | + contents: read # to check out repo (actions/checkout) |
| 113 | + steps: |
| 114 | + - uses: actions/checkout@v7 |
| 115 | + - uses: actions/setup-node@v7 |
| 116 | + with: |
| 117 | + node-version: 24 |
| 118 | + registry-url: https://registry.npmjs.org/ # set this option to set up npm authentication |
| 119 | + - run: npm install |
| 120 | + - run: npm build |
| 121 | + - uses: changesets/action/publish@v2 |
| 122 | + env: |
| 123 | + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # pass the token here |
| 124 | +``` |
| 125 | + |
| 126 | +> [!TIP] |
| 127 | +> Pass `registry-url: https://npm.pkg.github.com/` and `NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}` to publish to the GitHub Package Registry instead of npm. |
| 128 | + |
| 129 | +Internally, `actions/setup-node` will set up a `.npmrc` file that looks like this: |
| 130 | + |
| 131 | +```ini |
| 132 | +//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} |
| 133 | +``` |
| 134 | + |
| 135 | +This syntax allows to authenticate with the npm registry only when the `NODE_AUTH_TOKEN` environment variable is set, which is the safer approach than storing the token directly in the `.npmrc` file. |
| 136 | + |
| 137 | +### Manual Setup |
| 138 | + |
| 139 | +For advanced use cases, you can also set up the [`~/.npmrc` file](https://docs.npmjs.com/cli/configuring-npm/npmrc) manually. Make sure to remove the `registry-url` option from `actions/setup-node` to prevent conflicts with your custom `.npmrc` file. |
| 140 | + |
| 141 | +For example, if you need to publish different scopes to different registries, you can set up the `~/.npmrc` file like below: |
| 142 | + |
| 143 | +```yaml |
| 144 | +- run: | |
| 145 | + cat << 'EOF' > ~/.npmrc |
| 146 | +
|
| 147 | + # For unscoped packages, publish to the default npm registry |
| 148 | + //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} |
| 149 | +
|
| 150 | + # For @foo/* packages, publish to a custom registry |
| 151 | + @foo:registry=https://my-registry.com/ |
| 152 | + //my-registry.com/:_authToken=${NODE_FOO_AUTH_TOKEN} |
| 153 | +
|
| 154 | + # For @bar/* packages, publish to the GitHub Package Registry |
| 155 | + @bar:registry=https://npm.pkg.github.com/ |
| 156 | + //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} |
| 157 | +
|
| 158 | + EOF |
| 159 | +``` |
| 160 | + |
| 161 | +### Package Managers Edge Cases |
| 162 | + |
| 163 | +#### pnpm |
| 164 | + |
| 165 | +An `.npmrc` file in a project directory with pnpm does not support environment variables due to [security reasons](https://pnpm.io/blog/2026/06/11/env-variables-in-repository-npmrc). As such, it's recommended to set up in the home directory instead. This is also the general recommendation for other package managers to not mix potential existing config setups in projects. |
| 166 | + |
| 167 | +#### yarn |
| 168 | + |
| 169 | +[Yarn](https://yarnpkg.com) does not support the `.npmrc` file, compared to every other package managers that do. To set up authentication for yarn, use a [`~/.yarnrc.yml` file](https://yarnpkg.com/configuration/yarnrc) instead: |
| 170 | + |
| 171 | +```yaml |
| 172 | +- run: | |
| 173 | + cat << 'EOF' > ~/.yarnrc.yml |
| 174 | + npmAuthToken: "${NODE_AUTH_TOKEN}" |
| 175 | + EOF |
| 176 | +``` |
| 177 | + |
| 178 | +For advanced use cases, similar to the `.npmrc` example above, the equivalent looks something like this: |
| 179 | + |
| 180 | +```yaml |
| 181 | +- run: | |
| 182 | + cat << 'EOF' > ~/.yarnrc.yml |
| 183 | +
|
| 184 | + npmAuthToken: "${NODE_AUTH_TOKEN}" |
| 185 | +
|
| 186 | + npmScopes: |
| 187 | + foo: |
| 188 | + npmRegistryServer: "https://my-registry.com/" |
| 189 | + npmAuthToken: "${NODE_FOO_AUTH_TOKEN}" |
| 190 | + bar: |
| 191 | + npmRegistryServer: "https://npm.pkg.github.com/" |
| 192 | + npmAuthToken: "${GITHUB_TOKEN}" |
| 193 | +
|
| 194 | + EOF |
| 195 | +``` |
| 196 | + |
| 197 | +#### Miscellaneous |
| 198 | + |
| 199 | +Other package managers may also support (or recommend) configuring the tokens in their own configuration files. Check their documentation for more information. |
0 commit comments