LoginSignup
10
1

NextAuthでパスワード認証を実装する上での注意点

Last updated at Posted at 2023-09-20

はじめに

NextAuthでCredentials Providerを使ってDBでユーザーを管理しながらパスワード認証を実装するといくつか注意点があります。

注意点

セッション管理にDBが使えない

一番の注意点としては「セッション管理」にDBが使えないことです。
adapter(例:PrismaAdapter等)を設定すると、デフォルトでSession Strategyがdatabaseにされるので注意が必要です。
ここで、strategydatabaseのままだと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 引数の値を詰めた方がいいです。

実装例

/app/api/auth/[...nextauth]/route.ts
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 };

10
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
1