🚀

node の勉強がてら JWT の認証システムを作ってみた

2024/03/16に公開

node を勉強するために、JWT の認証システムを作ってみました。
クラス図は ChatGPT に書かせて、手直ししたやつになります。

クラス図

JWT での認証を行うためによくあるある(だと思っている)RS256 を作った JWT 検証をやってみます。

とりあえず JWT 用の鍵を作る

// https://nodejs.org/api/crypto.html
import crypto from 'crypto'

// Generate a key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
  modulusLength: 2048,
  publicKeyEncoding: { type: "spki", format: "pem" },
  privateKeyEncoding: { type: "pkcs8", format: "pem" },
});

クラス図にあわせてドメインモデル作成

import jwt from 'jsonwebtoken'

class UserAccount {
  username: string
  password: string
  email: string
  profile: UserProfile

  static create(email: string): UserAccount {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    if (!emailRegex.test(email)) {
      throw new Error('無効なメールアドレスです。')
    }

    const account = new UserAccount()
    account.#generatePassword()
    account.username = email.split('@')[0]
    account.email = email
    return account
  }

  updateProfile(firstName: string, lastName: string): UserProfile {
    this.profile = new UserProfile()
    this.profile.firstName = firstName
    this.profile.lastName = lastName
    return this.profile
  }

  #generatePassword(): void {
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()'
    let password = ''
    for (let i = 0; i < 12; i++) {
      password += characters.charAt(Math.floor(Math.random() * characters.length))
    }
    this.password = password
  }
}

class UserProfile {
  firstName: string
  lastName: string
}

class AuthenticationService {
  #PRIVATE_KEY = privateKey
  #PUBLIC_KEY = publicKey

  generateToken(username: string): string {
    const token = jwt.sign({ username }, this.#PRIVATE_KEY, { algorithm: 'RS256', expiresIn: '1h' })
    return token
  }

  verifyToken(token: string): string {
    const payload = jwt.verify(token, this.#PUBLIC_KEY, { algorithm: 'RS256' })
    return payload.username
  }
}

あとはドメインモデル使ってみる

AuthenticationService を使用して JWT トークンを作成します。

const service = new AuthenticationService()
const token = service.generateToken(account.username)

const payload = token.split('.');
const header = Buffer.from(payload[0], 'base64').toString('utf-8');
const claim = Buffer.from(payload[1], 'base64').toString('utf-8');
console.log(`Header: ${header}`);
console.log(`Claim: ${claim}`);

// Header: {"alg":"RS256","typ":"JWT"}
// Claim: {"username":"inoue","iat":1710581161,"exp":1710584761}

作った JWT トークンを検証すると正常にクレームが取得できたのが確認できました。

const service = new AuthenticationService()
const result = service.verifyToken(token)
console.log(`Success: ${result}`)
// Success: inoue

失敗するパターンも確認します。

const service = new AuthenticationService()
try {
  service.verifyToken('hoge');
} catch (error) {
  console.log(error)
}
// JsonWebTokenError: jwt malformed

さいごに

JWT 使って使ってよくあるある認証システムを作ることを確認できましたね!
次はこのコードのテストコードとか書きたいなと妄想しています。

GitHubで編集を提案

Discussion