はじめに
NextAuthでCredentials Providerを使ってDBでユーザーを管理しながらパスワード認証を実装するといくつか注意点があります。
注意点
セッション管理にDBが使えない
一番の注意点としては「セッション管理」にDBが使えないことです。
adapter(例:PrismaAdapter等)を設定すると、デフォルトでSession Strategyがdatabase
にされるので注意が必要です。
ここで、strategy
がdatabase
のままだとCredentials
で認証した場合、session
情報が取得できなくなります(嫌がらせ?)。
また、ここが混乱ポイントなのですが、セッション管理にDBが使えない=ユーザー管理にDBが使えないではないです。
あくまでも、セッション管理にDBが使えなくなるだけでadapterが設定されている以上、ユーザー情報はDBに永続化できます。
また、これに伴い、公式で作れと言われているSession
テーブルは使われないので作る必要はありません。
ユーザー登録
ユーザー登録(及びメールアドレス到達確認やパスワードリセット)は独自実装する必要があります。
他のプロバイダーと組み合わせて実装する場合は他のプロバイダーで登録されたEメールアドレスでは登録できないようにするか、メール受信確認が完了するまではサービスでの変更権限を絶対に与えないようにしてください。
最悪、アカウントが乗っ取られる可能性があります。
ユーザー情報の更新時
これは、パスワード認証(Credentials Provider)の注意点というかJWTを使う上での注意点ですが、ユーザー情報が更新されたときには、session.update()を呼び出した後に、jwt
コールバックの update
トリガーで情報を詰めなおす必要があります。これをやらないと次回ログインするまで情報が更新されません。
また、この場合、user
引数にDBの情報は入ってこないため、自分でDBから情報を取得する必要があります。
sessionコールバックではtoken引数を使う
sessionコールバックはめちゃくちゃ呼ばれるので、独自にデータを追加する場合は毎回DBからデータを取得するのではなく、JWTの情報を活用するように token
引数の値を詰めた方がいいです。
実装例
import NextAuth, { NextAuthOptions } from 'next-auth';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import CredentialsProvider from 'next-auth/providers/credentials';
import bcrypt from 'bcrypt';
import prisma from '@/app/prismaClient';
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: 'jwt',
},
providers: [
CredentialsProvider({
name: 'Password',
credentials: {
email: { label: 'Eメールアドレス', type: 'text' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials) return null;
const { email, password } = credentials;
const user = await prisma.user.findUnique({ where: { email } });
if (!user || !user.password) return null;
const isPasswordValid = await bcrypt.compare(password, user.password);
return isPasswordValid ? user : null;
},
}),
],
callbacks: {
async jwt({ token, trigger, user }) {
if (trigger === 'signIn') {
token.role = user.role;
token.emailVerified = user.emailVerified ? user.emailVerified : null;
}
if (trigger === 'update') {
const dbData = await prisma.user.findUnique({
where: { id: token.sub },
});
if (dbData === null) return token;
token.name = dbData.name;
token.email = dbData.email;
token.picture = dbData.image;
token.role = dbData.role;
token.emailVerified = dbData.emailVerified
}
return token;
},
async session({ session, token }) {
if (session?.user?.email === undefined || !session.user.email)
return session;
session.user.id = token.sub ? token.sub : '';
session.user.emailVerified = token.emailVerified;
session.user.role = token.role;
return session;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };