NextAuth(Auth.js)でのemail, passwordログイン時の処理を追ってみる
はじめに
NextAuthでemail, passwordでの認証をするには、ProviderとしてCredentialsProvider
を設定して実装する必要があります。
以下のように諸々設定することになるかと思います。
/**
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
*
* @see https://next-auth.js.org/configuration/options
*/
export const authOptions: NextAuthOptions = {
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.id = user.id;
}
return token;
},
},
providers: [
CredentialsProvider({
credentials: {
email: { type: "text" },
password: { type: "password" },
},
async authorize(credentials) {
// ...
},
}),
],
};
この時に、optionで設定した内容がどのようにサインイン処理に絡んでくるのかを知っておきたくなったのでNextAuthのコードをかいつまんで追ってみました。
signIn
Credentials Providerを使ってsigninするときの処理の一部を追ってみます。
以下のようにemai, passwordを引数にsignIn
メソッドを呼びだしたとします。
import { signIn } from "next-auth/react";
signIn("credentials", { email: "hoge@example.com", password: "hogehoge", redirect: false })
signIn()
のコードはこちらになります。
このメソッド内で
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
に対してPOSTリクエストを行っています。
_signInUrl
はhttp://localhost:3000/api/auth/callback/credentials?
となります。
http://localhost:3000/api/auth/callback/credentials?
へのリクエスト処理を追う
AuthHandler()
が呼ばれ、
が呼ばれます。
ここでは、HTTPリクエストメソッドやパスでその後の処理を振り分けています。
今回はPOST
でcallback
なので
で
const callback = await routes.callback({
body: req.body,
query: req.query,
headers: req.headers,
cookies: req.cookies,
method,
options,
sessionStore,
})
が呼ばれます。
routes.callback
このメソッドは以下になります。
このメソッド内で
user = await provider.authorize(credentials, {
query,
body,
headers,
method
});
という箇所があり、これがNextAuthのoptionで設定したauthorize
になります。
providers: [
CredentialsProvider({
credentials: {},
async authorize(credentials) {
// ...
},
}),
],
第一引数は以下のようにobjectになっています。
// credentials
{
email: 'hoge@example.com',
password: 'hogehoge',
redirect: 'false',
csrfToken: 'd202ec1cb30e52f8b7ee910acc9a6efd1107c7c4d39b09a9731f157b2149a9dd',
callbackUrl: 'http://localhost:3000/',
json: 'true'
}
そのため、authorize()
では最初にsignIn()
で渡したemail, passwordを使うことができます。
このemail, passwordを使ってログインさせるuserを取得することができます。
authorize()
でuserを取得した後の処理を追っていきます。
const isAllowed = await callbacks.signIn({
user,
// @ts-expect-error
account,
credentials,
})
という箇所があります。
これがNextAuthのoptionの
...
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
// ....
},
の部分になります。
ちなみに、optionとして設定していなかったらデフォルトが使われます。
callbacksのデフォルトはここにあります。
ここでは取得したuserにサインインを許可していいかをチェックすることができます。
このチェックの後は、
が続き、const token = await callbacks.jwt({
token: defaultToken,
user,
account,
isNewUser: false,
trigger: "signIn"
});
で、これがNextAuthのoptionの
...
callbacks: {
async jwt({ token, account, profile }) {
// ...
}
}
...
となります。
実際の値としては例えばログインuserとして以下のようなuserを取得していた時、
const user = { id: "1", name: "J Smith", email: "jsmith@example.com" };
以下のようになります。
defaultToken {
name: 'J Smith',
email: 'jsmith@example.com',
picture: undefined,
sub: '1'
}
user { id: '1', name: 'J Smith', email: 'jsmith@example.com' }
account {
providerAccountId: '1',
type: 'credentials',
provider: 'credentials'
}
token {
name: 'J Smith',
email: 'jsmith@example.com',
picture: undefined,
sub: '1'
}
その後の処理はjwt()
の返却値を暗号化しCookieに含めて、
http://localhost:3000/api/auth/callback/credentials?
のResponseとなります。
http://localhost:3000/api/auth/callback/credentials?
へのResponseが返ってきてからの処理
singIn()
での
http://localhost:3000/api/auth/callback/credentials?
へのリクエストが完了してからの処理を見ていきます。
といってもあとは特に気になることはなくて、signInに成功した時にリダイレクトするのかを見て、成功可否をreturnしています。
email, password認証する時のoptionまとめ
コードをかいつまんで追った結果、Optionとしてはデフォルトでよければ以下だけでemail, password認証が実現できるということがわかりました。
/**
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
*
* @see https://next-auth.js.org/configuration/options
*/
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
// ここのKeyは下のauthorize()のcredentialsに型が追加されるので追加してる
credentials: {
email: { type: "text" },
password: { type: "password" },
},
async authorize(credentials) {
const email = credentials?.email;
const password = credentials?.password;
// TODO: email, passwordを使ってuserを取得する
const user = { id: "1", name: "J Smith", email: "jsmith@example.com" };
if (user) {
return user;
} else {
return null;
}
},
}),
],
};
authorize()
でユーザーを取得した後に、そのユーザーのsigninを弾きたい時はsingIn()
で、何かしらの判定処理を入れてfalseで返す感じです。
...
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
// ....
},
(ここで弾くのと、authorize()
で弾くのとなにか違いがあるんだろうか)
jwt()
の方は、token(Cookie)にさらに情報を含めたい時に使う感じかと。
...
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.id = user.id;
}
return token;
},
https://next-auth.js.org/configuration/callbacks#jwt-callback ではアクセストークンを含めている。
Discussion