[WIP] Next.js (Auth.js) と Backend API による GitHub 認証の実装例
🎯 目的
Next.js (Auth.js) と Backend API 構成によるアプリにおける OAuth2 認証の実装例をまとめて、再現できるようにする
💡 前提
今日の Web サービスでは、Frontend と Backend で切り離されたシステム構成にしていることが多いように思われます。例えば、Frontend には、昨今人気の Next.js を採用し、Backend API には、PHP の Laravel や Python の FastAPI、Ruby の Ruby on Rails などを採用しているなどといった構成です。私も個人開発では Frontend に Next.js を採用し、 Backend API には FastAPI を採用し、開発をしています。
そんな中、 Next.js の認証ライブラリ Auth.js (NextAuth.js v5) を利用した、Next.js と Backend API による OAuth 認証の実装がわからなかったのでこの記事に調べた内容をまとめます。
システムの前提としては以下の通りです。
💡 Auth.js による OAuth 認証のフロー
Auth.js で OAuth 認証を行う際のフローは以下のページにまとまっています。しかし、このページでは Frontend × Backend API 構成のシステムにおける認証フローがありません。。。
🛠️ 設計
Next.js (Auth.js) と Backend API による OAuth 認証フローとして、以下の記事を参考にしました。
上記の記事では、 OAuth プロバイダーである GitHub から発行された一時コードを Frontend から Backend API に送信し、最終的に Backend API が発行したアクセストークンを Frontend に返却しています。(ここでいうアクセストークンは、Backend API が独自に発行したアクセストークンのことであり、GitHub のアクセストークンではありません。)
本記事でも、Frontend から一時コードを Backend API に送信し、Backend API がアクセストークンを生成し、返却する。以後 Frontend は返却されたアクセストークンをヘッダーに指定して、 Backend API に送信するようにします。
💻 実装
CLIENT_ID
/ CLIENT_SECRET
を発行する
💻 GitHub
CLIENT_ID
と CLIENT_SECRET
を発行する : Settings > Developer settings > OAuth Apps でRegister a new application
ボタンを押し、以下のように入力します。
💻 Next.js (Auth.js) の改修
export default {
providers: [
Github({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
token: `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/github/token`, // 一時コードを送信する Backend API のパスを指定する
userinfo: {
async request({
tokens
}: {
tokens: {access_token: string; refresh_token: string; token_type: 'bearer'} // Backend API から返却されたアクセストークン
}) {
// Backend API で返却されたアクセストークンをもとにユーザー情報を取得する
const me = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/users/me`, {
headers: { 'Authorization': `bearer ${tokens.access_token}` }
}).then((res) => res.json());
return {
email: me.email_address,
accessToken: tokens.access_token
};
}
},
profile(profile) {
return {
email: profile.email,
accessToken: profile.accessToken
}
}
}),
],
// ...
}
import json
from typing import Annotated
import requests
from fastapi import APIRouter, Depends, Form
class AccessTokenRequest:
def __init__(
self,
code: Annotated[str, Form()],
redirect_uri: Annotated[str, Form()],
code_verifier: Annotated[str, Form()],
grant_type: Annotated[str, Form()]
):
self.code = code
self.redirect_uri = redirect_uri
self.code_verifier = code_verifier
self.grant_type = grant_type
class GitHubResource:
router = APIRouter(prefix='/auth/github', tags=['Auth'])
GITHUB_API = 'https://api.github.com'
def __init__(self, client_id: str, client_secret: str):
self.__client_id = client_id
self.__client_secret = client_secret
self.router.add_api_route("/token", self.token, methods=["POST"], response_model=Token)
def token(self, request: AccessTokenRequest = Depends()) -> dict:
headers = {'Authorization': f"Bearer {self.__access_token_from(request.code)}"}
user = requests.get(f'{self.GITHUB_API}/user', headers=headers).json()
emails = requests.get(f'{self.GITHUB_API}/user/emails', headers=headers).json()
# ここでユーザー認証を行う。
# - ex) 該当メールアドレスを保持するユーザーがあれば、GitHubアカウントを紐づけて認証完了とする
# - ex) 該当メールアドレスを保持するユーザーアカウントが存在しなければ、ユーザー登録を行い、認証完了とする
return {'access_token': '...', 'refresh_token': '...', 'token_type': 'bearer'}
def __access_token_from(self, code: str) -> str:
access_token = requests.post(
'https://github.com/login/oauth/access_token',
headers={'Accept': 'application/json'},
params={'client_id': self.__client_id, 'client_secret': self.__client_secret, 'code': code}
).json()
return access_token['access_token']
📝 今後の調査
今回のような Next.js と Backend API による OAuth 認証方法について調べている中で、以下の Issue を見つけました。この Issue では、 REST API 用の Adapter を実装して、Frontend × Backend の認証を実現しているので、今後この方法についても調査したいと思います。
Discussion