Skip to content

npmパッケージ公開のOIDC Trusted publishingへの移行

#Classic tokenの廃止と代替手段

2025年12月9日、npmのClassic tokenが完全に廃止された(npm classic tokens revoked, session-based auth and CLI token management now available)。

代わりにローカルではnpm loginによるセッションベースの認証、GitHub ActionsなどのCI/CD環境では2FAをBypassしたGranular Access TokensまたはOIDC Trasted publishingを利用できる。

#Trasted publishingのメリット

npm公式がTrasted publishingを推奨している(Security best practices: Prefer trusted publishing over tokens)。

Trasted publishingは、OpenID Connect (OIDC) を利用しGitHub Actionsとnpmが直接信頼関係を結ぶ仕組みである。 実際、CI/CD環境においてTrasted publishingはTokenを用いた方法の上位互換と言える。

Trasted publishingにはTokenと比較して次の特性がある:

  • 漏洩リスクがない:Secretが存在せず、CI実行時に短命なトークンが発行されるのみ。
  • 管理コストがない:定期的なトークン再発行やSecretのローテーションをする必要がない

#Trusted Publishingへの移行

#STEP 1: npmパッケージ設定の変更

まず、npmでパッケージのSettingsからTrusted Publisherを登録する。

  1. npm公式サイトにログインし、対象パッケージのページへ移動。
  2. Settingsタブをクリックし、Trusted PublisherにあるSelect your publisherから利用するCI/CD環境を選択する。
  3. GitHub Actionsを選択した場合、以下の情報を入力する。
    • Organization or user: GitHub Actionsを実行するリポジトリのOrganizationまたはUser名
    • Repository: GitHub Actionsを実行するリポジトリ名
    • Workflow filename: npm publishを実行するワークフローファイル名
    • Environment name: GitHub Actions environmentを利用している場合は環境名を指定

この設定により、「指定したリポジトリ」の「指定したワークフローファイル」からのみ、Publish権限が発行されるようになる。

alt text

#STEP 2: GitHub Actionsワークフローの修正

次に、GitHub ActionsのYAMLファイルを修正する。主な変更点は permissions の追加と env の削除。

修正前の例:

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # ← 削除

修正後の例:

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # ← OIDCトークン発行のために必須
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
# npm v11.5.1以上が必要
# Node.js v20やv22、古い特定のv24バージョンを利用している場合、別途npmの更新が必要
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'
# - run: npm install -g npm@latest
- run: npm publish

#Trasted publishingの制約

npmのTrasted publishingは、現状いくつか制約がある。

  • Trasted publisherが1つに限定される。
    • 特定のリポジトリの特定のファイルからのみしか利用できない。
  • プライベートリポジトリではprovenanceを生成できない。
    • npm publish --provenanceを実行するとpublishに失敗する。
  • Trasted publisherとpackage.jsonのrepositoryフィールドの一致が必要
    • repositoryが未記入または一致しない場合、422 Unprocessable Entityエラーが発生する。
    • "repository": "https://github.com/orgs/repository-name.git"といった指定が必要。
    • これはprovenanceを生成しない場合でも発生する。
  • npm v11.5.1以上が必要。
    • 最新のNode.js v24以上であれば問題はないが、それ未満では別途npmの更新が必要。
    • それと分かるエラーは出ないため注意。遭遇した例では404エラーが発生した。
  • 利用できるCI/CD環境はnpmが対応している環境に限定される。

#GitHub Actionsワークフローの実装例

デフォルトブランチにコミットが行われる度に、可能ならリリースまたはアルファリリースを行うリポジトリの実装例。 Trashed publisherは単一のワークフローファイルに紐付ける必要があるため、Trasted publishingを行う場合リリースとアルファリリースでワークフローを分けることは出来ない。

name: publish
on:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CI: true
permissions:
contents: read
id-token: write
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
can_publish: ${{ steps.can-publish.outputs.can_publish }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build package
run: yarn build
- name: Check if can publish
id: can-publish
run: |
npx can-npm-publish --verbose && echo "can_publish=true" >> $GITHUB_OUTPUT || echo "can_publish=false" >> $GITHUB_OUTPUT
continue-on-error: true
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 1
publish-release:
needs: check
if: needs.check.outputs.can_publish == 'true'
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
- uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Publish
run: |
npm publish
publish-alpha:
needs: check
if: needs.check.outputs.can_publish != 'true'
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v5
with:
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
- uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Update version
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
yarn version --prepatch --preid alpha-$(($(date +%s%N)/1000000))-$(git rev-parse --short HEAD)
- name: Publish
run: |
npm publish --tag alpha