NextAuthで指定したギルドユーザー限定のDiscordログインを実装する
概要
こんにちは!現在仕事で東京で 1 ヶ月ホテル生活をしていますユウトです!
今回は自分の所属しているコミュニティで、NextAuth を使って Discord ログイン機能を実装し、その中で特定のギルドに入っていないとログインできないような機能を作りました!
実装していく中であんまり参考になるような記事がなかったので、メモがてら書きます!
使用技術
- React v18.2.0
- Next.js v13.1.1 (app ディレクトリは使ってないです)
- next-auth v4.18.7
…etc
早速本題
Discord OAuth2 の設定
まずは Discord ログインの機能を使用するために、Discord の OAuth2 の設定をします。
- https://discord.com/developers/applications にアクセスをして、Discord ログインをする
-
New Application
をクリックし、アプリケーションの名前を入力する
-
リダイレクト URL を入力する
Redirects という場所に、http://localhost:3000/api/auth/callback/discord を追加する
(本番とかであれば、http://twitter.com/api/auth/callback/discord みたいな感じでもう一つ追加すれば OK)
NextAuth の設定
インストール
$ yarn add next-auth
Provider の設定
import NextAuth from "next-auth";
import DiscordProvider from "next-auth/providers/discord";
export const authOptions = {
providers: [
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
}),
],
};
export default NextAuth(authOptions);
ここまでで一旦確認
実はここまでで Discord ログインは可能です。
import { signIn, useSession } from "next-auth/react";
export const LoginPage: NextPage = () => {
const { data: session } = useSession();
return <button onClick={signIn}>Discordでログイン</button>;
};
みたいな感じで、ボタン と NextAuth
から import した signIn
メソッドを使用すると、ログインができるようになっているはずです。
そしてログインに成功すると、useSession
の session
にログインしたユーザーの情報が入っているのが確認できます。
ただ、これだと特定のギルドに入っているかというチェックができていないので、次にそのチェックを実装していきます。
ギルドに入っているかどうかを確認するための準備
ギルド情報を取得する許可を与える
import NextAuth from "next-auth";
import DiscordProvider from "next-auth/providers/discord";
export const authOptions = {
providers: [
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
+ authorization: {
+ params: { scope: 'identify email guilds' },
+ },
}),
],
};
export default NextAuth(authOptions);
これでギルド情報を取得する許可を与えました。
ギルド情報を取得するための API を叩く
-
アプリ内で、さっきと同じ方法で普通にログインをする
-
https://discord.com/developers/docs/resources/user#get-current-user-guilds を使って、ログインしたユーザーのギルドを取得する
NextAuth に便利な callback があるので、それを使います。
pages/api/auth/[...nextauth].jsimport NextAuth from "next-auth"; import DiscordProvider from "next-auth/providers/discord"; export const authOptions = { providers: [ DiscordProvider({ clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET, authorization: { params: { scope: 'identify email guilds' }, }, }), ], + callbacks: { + /** + * sessionにaccessTokenと、user.idを追加 + */ + session: async ({ session, token }) => { + session.accessToken = token.accessToken; + if (session.user) { + session.user.id = token.id; + } + return session; + }, + + /** + * jwtにaccessTokenと、profile.idを追加 + */ + jwt: async ({ token, account, profile }) => { + if (account && account.access_token) { + token.accessToken = account.access_token; + } + if (profile) { + token.id = profile.id; + } + return token; + }, + }, + }; export default NextAuth(authOptions);
-
実際に api 叩く
pages/index.tsxexport const Home:FC = () => { const { data: session } = useSession(); const handleClick = async () => { const res = await fetch(`https://discordapp.com/api/users/@me/guilds`, { headers: { Authorization: `Bearer ${session?.accessToken}`, }, }); console.log('res', res); }; return <button onClick={handleClick}>ギルド情報を取得</button>; }
ここまでで、
- Discord でログインをする
- ログインユーザーの加入しているギルド情報を取得する
ということはできました。
ここから最後に、特定のギルドに入っていたらログイン成功の処理をやっていきます。
特定のギルドに入っていたらログイン成功
入っていて欲しいギルドの ID を指定する
-
ID は discord の web 版から確認できます。
-
https://discord.com/channels/10000000001/1000000002
の10000000001
のところが ID です。 -
以下のような型の値が返ってきます。
features: string[] icon: string id: string name: string owner: boolean permissions: number permissions_new: string
-
ギルドに加入しているかどうかを判定するメソッドを作ります
async isJoinGuild(accessToken: string): Promise<boolean> { const res: Response = await fetch("https://discordapp.com/api/users/@me/guilds", { headers: { Authorization: `Bearer ${accessToken}`, }, }); if (res.ok) { const guilds: Guild[] = await res.json(); return guilds.some((guild: Guild) => guild.id === guildId); } return false; },
最後にこの作成したメソッドを使用します!
isJoinGuild を使用する
- callback 内の、signIn というメソッドの中で使用すると、signIn した際に自動で発火するようにしてくれます。
- この際に、false を返すとログイン失敗になります。
callbacks: {
signIn: async ({ account, user, profile }) => {
if (account == null || account.access_token == null) return false;
return await isJoinGuild(account.access_token)
},
}
ここまでで、特定のギルドに入っていたらログイン成功という処理ができました!!
まとめ
最後に一連の流れで実装した[…nextauth].ts
内のコードを確認します!
import { discordClientId, discordClientSecret } from '@/constants/env';
import { DiscordClient } from '@/lib/discord-client';
import NextAuth, { type NextAuthOptions } from 'next-auth';
import DiscordProvider from 'next-auth/providers/discord';
async isJoinGuild(accessToken: string): Promise<boolean> {
const res: Response = await fetch(`${BASE_DISCORD_URL}/api/users/@me/guilds`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (res.ok) {
const guilds: Guild[] = await res.json();
return guilds.some((guild: Guild) => guild.id === guildId);
}
return false;
},
export const authOptions: NextAuthOptions = {
providers: [
DiscordProvider({
clientId: discordClientId,
clientSecret: discordClientSecret,
authorization: {
params: { scope: 'identify email guilds' },
},
}),
],
/**
* ログイン情報を保存できる
*/
session: { strategy: 'jwt' },
callbacks: {
/**
* sessionにaccessTokenと、user.idを追加
*/
session: async ({ session, token }) => {
session.accessToken = token.accessToken;
if (session.user) {
session.user.id = token.id;
}
return session;
},
/**
* jwtにaccessTokenと、profile.idを追加
*/
jwt: async ({ token, account, profile }) => {
if (account && account.access_token) {
token.accessToken = account.access_token;
}
if (profile) {
token.id = profile.id;
}
return token;
},
/**
* ログインボタンを押した際に発火される
*/
signIn: async ({ account, user, profile }) => {
if (account == null || account.access_token == null) return false;
return await isJoinGuild(account.access_token);
},
},
};
export default NextAuth(authOptions);
あとは普通にボタンに nextauth の signIn を設定してあげれば、ログインできるようになります!
最後に
今回は、Discord でログインをする方法を紹介しました!
この記事が、誰かの役に立てば幸いです!
Discussion
記事ありがとうございます。とても参考になりました。
些細なことですが
jwtの最後の方、括弧のインデントがおかしい気がします
(括弧の数は合ってそうです)