🛡️

Discord サーバーを守る ロール認証アプリを作成した話

2025/02/02に公開2

はじめに

私は通信高校に通っており、同好会の運営を行っています
しかし、昨日サーバーの招待リンクが学園外に漏れてしまい、第三者が不正に参加する事態が発生しました
この問題を解決するために、特定のメールアドレスを持つユーザーだけがサーバーに参加できるようにする認証機能を実装しました

本記事では、このアプリケーションの実装内容や、開発において工夫した点を紹介します

アプリの概要

このアプリは、Cloudflare Workers と Hono を使用して、Discord サーバーのメンバーに特定のロールを付与する仕組みを提供します
認証には Discord OAuth2 と Google OAuth2 を利用し、指定したメールアドレスのドメインを持つユーザーのみが認証を通過できるようにしています

主な機能

  • Discord OAuth2 を利用したユーザー認証
  • Google OAuth2 でメールアドレスを取得し、ドメインをチェック
  • 許可されたメールアドレスのユーザーに対して Discord の特定のロールを付与
  • JWT と CSRF 対策を施し、セキュリティを強化

実装で頑張った部分

1. CSRF 対策

OAuth2 の state パラメータを利用し、CSRF 攻撃を防ぐ仕組みを実装しました
具体的には、認証開始時にランダムな state 値を生成し、クッキーに保存しておき
認証後に返ってきた state 値と照合し、一致しない場合は認証を拒否するようにしました
index.ts (Lines 21 to 36 in 9e3c108)

// Function to generate state for CSRF protection
async function generateState(): Promise<string> {
  const buffer = new Uint8Array(32);
  crypto.getRandomValues(buffer);
  const state = Array.from(buffer)
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
  return state;
}

// Validate the CSRF token
function validateState(c: Context, state: string): boolean {
  const savedState = getCookie(c, "oauth_state");
  deleteCookie(c, "oauth_state");
  return savedState === state;
}

2. JWT によるセッション管理

Discord ユーザー ID を安全に保存するため、JWT を使用しました
JWT の署名には環境変数 JWT_SECRET を用いることで、安全性を確保しています
index.ts (Lines 118 to 125 in 9e3c108)

// Save discordUserId safely with JWT
const jwt = await sign({ discordUserId }, c.env.JWT_SECRET);
setCookie(c, "discord_user", jwt, {
  httpOnly: true,
  secure: true,
  sameSite: "Lax",
  maxAge: 600,
});

3. メールドメインのチェック

Google OAuth2 を使用して取得したメールアドレスのドメインをチェックし、特定のドメインを持つユーザーのみを許可します
index.ts (Lines 212 to 216 in 9e3c108)

const allowedDomains = ["nnn.ed.jp", "n-jr.jp", "nnn.ac.jp"];
const emailDomain = email.split("@")[1];
if (!allowedDomains.includes(emailDomain)) {
  return c.json({ error: "Email domain not allowed" }, 403);
}

4. Discord ロールの付与

認証を通過したユーザーには、Discord の API を用いて特定のロールを付与します
index.ts (Lines 245 to 255 in 9e3c108)

// Assign a role
const roleResponse = await fetch(
  `${c.env.DISCORD_API_BASE}/guilds/${c.env.DISCORD_GUILD_ID}/members/${discordUserId}/roles/${c.env.DISCORD_ROLE_ID}`,
  {
    method: "PUT",
    headers: {
      Authorization: `Bot ${c.env.DISCORD_BOT_TOKEN}`,
      "Content-Type": "application/json",
    },
  },
);

まとめ

今回、Discord サーバーの安全性を向上させるために、ロール認証システムを実装しました
私にとって初めての JWT や CSRF 対策の実装でしたが、セキュリティの重要性を実感しながら学ぶ良い機会となりました
今後もより安全で使いやすい仕組みを考え、改善を続けていきたいと思います

GitHubで編集を提案

Discussion

LaPhLaPh

実はStudent Hubという機能があります!
その学校のドメインを持つユーザーのみが参加できるという機能です!!!()wait listなどはありますが、割と便利なので活用してみてください。

https://support.discord.com/hc/ja/articles/4406046651927-DIscord学生ハブFAQ

みなぎしみなぎし

コメントありがとうございます!
Student Hub も利用していますが、基本的には招待リンク経由で人が集まるため、このような対応となりました