jose の使い方

JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes

v6.2.2/週MIT認証
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

jose の使い方 — JavaScriptでJWT/JWS/JWE/JWKを扱う決定版ライブラリ

一言でいうと

jose は、JWT(JSON Web Token)の署名・検証・暗号化をはじめ、JWS・JWE・JWK・JWKSといったJOSE(JSON Object Signing and Encryption)仕様群をフルカバーするJavaScriptライブラリです。依存パッケージゼロで、Node.js・ブラウザ・Cloudflare Workers・Deno・Bunなど、Web標準のCrypto APIをサポートするあらゆるランタイムで動作します。

どんな時に使う?

  1. APIサーバーでJWTの発行・検証を行いたい時 — アクセストークンやIDトークンの署名・検証を安全に実装する場面
  2. JWKS(JSON Web Key Set)エンドポイントから公開鍵を取得して署名検証したい時 — Auth0やFirebase、Cognitoなど外部IdPのトークンを検証する場面
  3. JWEでペイロードを暗号化したい時 — トークンの中身を第三者に見られたくない場合に、署名だけでなく暗号化まで行う場面

インストール

# npm
npm install jose

# yarn
yarn add jose

# pnpm
pnpm add jose

Note: v6.x はESMのみの配布です。CommonJS(require)で使いたい場合はv5.xを検討してください。

基本的な使い方 — JWTの署名と検証

最も一般的なユースケースである「JWTの署名(発行)と検証」の例です。

import { SignJWT, jwtVerify, generateSecret } from 'jose'

// HS256用の共通鍵を生成
const secret = await generateSecret('HS256')

// JWTの署名(発行)
const token = await new SignJWT({ sub: 'user-123', role: 'admin' })
  .setProtectedHeader({ alg: 'HS256' })
  .setIssuedAt()
  .setIssuer('https://example.com')
  .setAudience('https://api.example.com')
  .setExpirationTime('2h')
  .sign(secret)

console.log(token)
// eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyLTEyMyIs...

// JWTの検証
const { payload, protectedHeader } = await jwtVerify(token, secret, {
  issuer: 'https://example.com',
  audience: 'https://api.example.com',
})

console.log(payload)
// { sub: 'user-123', role: 'admin', iat: 1718000000, iss: 'https://example.com', aud: 'https://api.example.com', exp: 1718007200 }
console.log(protectedHeader)
// { alg: 'HS256' }

よく使うAPI — jose の主要機能5選

1. SignJWT — JWTの署名

import { SignJWT, importPKCS8 } from 'jose'

const privateKeyPem = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqh...
-----END PRIVATE KEY-----`

const privateKey = await importPKCS8(privateKeyPem, 'RS256')

const jwt = await new SignJWT({ sub: 'user-456', scope: 'read write' })
  .setProtectedHeader({ alg: 'RS256', kid: 'my-key-id' })
  .setIssuedAt()
  .setExpirationTime('1h')
  .setNotBefore('0s')
  .setJti('unique-token-id-001')
  .sign(privateKey)

2. jwtVerify — JWTの検証

import { jwtVerify, importSPKI } from 'jose'

const publicKeyPem = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqh...
-----END PUBLIC KEY-----`

const publicKey = await importSPKI(publicKeyPem, 'RS256')

try {
  const { payload } = await jwtVerify(jwt, publicKey, {
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com',
    algorithms: ['RS256'],
    // clockTolerance で時刻のずれを許容(秒)
    clockTolerance: 5,
  })
  console.log('検証成功:', payload)
} catch (err) {
  if (err instanceof Error) {
    console.error('検証失敗:', err.message)
  }
}

3. createRemoteJWKSet — リモートJWKSによる検証

外部IdPのトークン検証で最も実用的なパターンです。

import { jwtVerify, createRemoteJWKSet } from 'jose'

// JWKSエンドポイントからキーセットを取得(キャッシュ付き)
const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks.json')
)

const { payload, protectedHeader } = await jwtVerify(token, JWKS, {
  issuer: 'https://auth.example.com/',
  audience: 'my-api',
})

console.log(protectedHeader.kid) // JWKSから自動的にマッチしたキーID
console.log(payload.sub)

4. generateKeyPair / generateSecret — 鍵の生成

import {
  generateKeyPair,
  generateSecret,
  exportJWK,
  exportSPKI,
  exportPKCS8,
} from 'jose'

// 非対称鍵ペアの生成(RSA)
const { publicKey, privateKey } = await generateKeyPair('RS256')

// JWK形式でエクスポート
const publicJwk = await exportJWK(publicKey)
console.log(publicJwk)
// { kty: 'RSA', n: '...', e: 'AQAB' }

// PEM形式でエクスポート
const spki = await exportSPKI(publicKey)
const pkcs8 = await exportPKCS8(privateKey)

// 対称鍵(シークレット)の生成
const secret = await generateSecret('HS256')
const secretJwk = await exportJWK(secret)
console.log(secretJwk)
// { kty: 'oct', k: '...' }

// EC鍵ペアの生成
const ecPair = await generateKeyPair('ES256')

5. EncryptJWT / jwtDecrypt — JWTの暗号化と復号

import { EncryptJWT, jwtDecrypt, generateKeyPair } from 'jose'

// RSA-OAEP + A256GCM で暗号化
const { publicKey, privateKey } = await generateKeyPair('RSA-OAEP-256')

// 暗号化
const encryptedJwt = await new EncryptJWT({ sub: 'user-789', secret_data: 'classified' })
  .setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
  .setIssuedAt()
  .setExpirationTime('30m')
  .encrypt(publicKey)

console.log(encryptedJwt)
// eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0...(5パート)

// 復号
const { payload, protectedHeader } = await jwtDecrypt(encryptedJwt, privateKey)
console.log(payload.secret_data) // 'classified'

ユーティリティ関数

検証前にトークンの中身を確認したい場合に便利です。

import { decodeJwt, decodeProtectedHeader } from 'jose'

const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'

// ⚠️ 署名検証なしでペイロードをデコード(デバッグ用途)
const claims = decodeJwt(token)
console.log(claims.exp) // 有効期限のUNIXタイムスタンプ

// ヘッダーのデコード
const header = decodeProtectedHeader(token)
console.log(header.alg) // 'RS256'
console.log(header.kid) // キーID

⚠️ decodeJwt は署名検証を行いません。本番コードでは必ず jwtVerify を使ってください。

類似パッケージとの比較

特徴josejsonwebtokenjwt-decode
JWT署名・検証❌(デコードのみ)
JWE(暗号化)
JWK / JWKS対応❌(別途jwks-rsaが必要)
依存パッケージ030
ブラウザ対応✅(Web Crypto API)❌(Node.js専用)
Edge Runtime対応
TypeScript型定義✅(同梱)✅(@types)✅(同梱)
ESM対応❌(CJSのみ)
メンテナンス状況活発低調活発

jsonwebtoken はNode.jsの crypto モジュールに依存しているため、Cloudflare WorkersやVercel Edge Functionsでは動作しません。エッジランタイムを使う場合は jose が事実上の標準です。

注意点・Tips

1. v6.xはESMオンリー

v6.xからCommonJS(require)のサポートが廃止されました。require('jose') は動作しません。CommonJSが必要な場合はv5.xを使うか、プロジェクトをESMに移行してください。

2. アルゴリズムの利用可能性はランタイム依存

Web Crypto APIの実装はランタイムによって異なります。例えば、一部のEdgeランタイムでは RS384 が使えないことがあります。本番環境のランタイムで事前にテストしてください。

// 対応アルゴリズムが不明な場合は try-catch で安全に
try {
  const { publicKey, privateKey } = await generateKeyPair('EdDSA')
} catch (err) {
  console.error('EdDSAはこのランタイムでサポートされていません')
}

3. createRemoteJWKSet のキャッシュ

createRemoteJWKSet は内部でHTTPレスポンスをキャッシュします。関数呼び出しのたびにインスタンスを作り直すとキャッシュが効かないため、アプリケーション起動時に一度だけ生成してモジュールスコープに保持してください。

// ✅ Good: モジュールスコープで一度だけ生成
const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks.json')
)

export async function verifyToken(token: string) {
  return jwtVerify(token, JWKS)
}

// ❌ Bad: リクエストごとに生成
export async function verifyToken(token: string) {
  const jwks = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'))
  return jwtVerify(token, jwks)
}

4. エラーハンドリング

jose は用途別に細かいエラークラスを提供しています。適切にハンドリングすることで、ユーザーに正確なエラーメッセージを返せます。

import { jwtVerify, errors } from 'jose'

try {
  await jwtVerify(token, secret)
} catch (err) {
  if (err instanceof errors.JWTExpired) {
    console.error('トークンの有効期限切れ:', err.payload)
  } else if (err instanceof errors.JWTClaimValidationFailed) {
    console.error('クレーム検証失敗:', err.claim, err.reason)
  } else if (err instanceof errors.JWSSignatureVerificationFailed) {
    console.error('署名検証失敗')
  } else {
    throw err
  }
}

5. clockTolerance を設定する

分散システムではサーバー間の時刻ずれが発生します。jwtVerifyclockTolerance オプションで数秒の許容範囲を設定しておくと、不要な検証エラーを防げます。

まとめ

jose は、依存ゼロ・マルチランタイム対応・JOSE仕様フルカバーという三拍子が揃った、JavaScriptにおけるJWT/JWS/JWE処理のデファクトスタンダードです。特にEdge RuntimeやCloudflare Workersなど、Node.jsネイティブモジュールが使えない環境では唯一の実用的な選択肢と言えます。jsonwebtoken からの移行先としても最適なので、新規プロジェクトでは jose を第一候補にしてください。