Next.jsで認証するのに何を使おうか・・
せっかく**Next.js使ってるしNextAuthを使ってみよう!**ということで、軽い気持ちで導入したらドハマリしました。
今回GitHubのアクセストークンを取得する認証の仕組みを作りたいなと思いNextAuthを使いました。
GitHubで認証してアクセストークンの取得でハマったポイントを書きます。
Next.jsでAPIを定義
NextAuthではAPIを使うのでまずはNext.jsのAPI定義方法をさらっと。
Next.jsではpages/api
配下にファイルを配置することで、APIのエンドポイントを定義することができます。
export default (req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ name: 'John Doe' }))
}
NextAuthでAPIを実装
NextAuthではNext.jsのAPIルーティングの機能を利用してpages/api/[...nextauth].jsを作ることによって認証に必要なAPIを定義することができます。
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
const options = {
site: process.env.SITE || 'http://localhost:3000',
// Configure one or more authentication providers
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}),
],
// A database is optional, but required to persist accounts in a database
database: process.env.DATABASE_URL,
}
export default (req, res) => NextAuth(req, res, options)
NextAuthのセッション管理の種類
NextAuthではセッション管理に2パターンあります。
- DB
- JWTトークン
オプションのdatabaseはDBを使う場合に指定が必要ですが、今回はJWTでのトークン発行を使いたいのでoptionsから消してしまって問題ありません。
もしdatabaseの指定がない場合はオプションのsession.jwtが自動的にtrueになり、JWTトークンによるセッション管理になります。
公式のオプションにあるsessionの項目にも記載があります。
// Use JSON Web Tokens for session instead of database sessions.
// This option can be used with or without a database for users/accounts.
// Note: `jwt` is automatically set to `true` if no database is specified.
セッションの取得
NextAuthでセッションを取得するにはuseSession()やgetSession()を使います。
そしてドキュメントを見るとデータは下記のような感じで返ってくるんだな、と理解したのですがここがひとつハマりポイントでした。
{
user: {
name: string,
email: string,
image: uri
},
accessToken: string,
expires: "YYYY-MM-DDTHH:mm:ss.SSSZ"
}
実際にuseSessionを使ってみると返ってくるデータが下記。
{
user: {
name: string,
email: string,
image: uri
},
expires: "YYYY-MM-DDTHH:mm:ss.SSSZ"
}
一番欲しいはずのaccessTokenがない・・。
callbacksを使ってaccessTokenを追加
session.jwtがtrueの場合はaccessTokenが含まれません。
そのため最初に紹介したoptionsで下記のようなcallbacksを追加します。
コメント頂いたので追記
GitHubのトークンをaccessTokenというキーで返却してしまっていますが、セッション管理のトークンとしての役割ではありません。(キー名がわかりにくい・・)
callbacks: {
session: async (session, token) => {
return Promise.resolve({
...session,
accessToken: token.account.accessToken
})
}
}
callbacks.sessionはセッション情報を取得しようとしたときに呼ばれ、返すデータを変更することできます。
デフォルトでは引数のsessionをそのまま返すため、tokenに含まれるaccessTokenをデータへ含めるようにします。
(上記追記の通り)
引数のsessionとtokenはそれぞれの下記のようデータです。
■session
interface Session {
user: {
name: string
email: string | null
image: string
}
expires: string
}
■token
interface Token {
user: {
name: string
email: string | null
image: string
}
account: {
provider: 'github'
type: string
id: number
refreshToken?: string
accessToken: string
accessTokenExpires: string
}
iat: number
expt: number
}
GitHubのscopeを設定
GitHubで欲しい情報に制限がかかっている場合はscopeを事前に設定した上でアクセストークンを取得する必要があります。
NextAuthでGitHub認証のscopeを設定するにはProviders.GitHubにscopeを追加します。
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
scope: 'repo read:org'
})
],
scopeの指定は文字列で複数指定する場合にはスペースをいれて並べることで指定可能です。
scopeの指定方法に注意
NextAuthのドキュメントでscopeの指定方法を見つけることができず、scopeの設定に苦戦してしまいました・・。
githubのGraphQLでちゃんとscopeついてるトークンのはずなのにプライベートが取得できない・・なぜ・・
— Koji Murakami (@koojy3) July 7, 2020
scopeは文字列で指定するのが正解ですが、配列で指定しても動いているような挙動になっていたんですよね。
scope: ['repo', 'read:org']
こんな感じです。
この指定方法では適切にscopeが設定されたトークンを取得できないので注意が必要です。
まとめ
わかってしまえばNextAuthはとても便利!という印象を受けましたが、ドキュメントのコードサンプルがもうちょっとあると嬉しいです。
(ちゃんとドキュメントを読んで、コードを読めばいいんですけど・・)