👨‍✈️

msw + NextAuth.js(+ Next.js)で、認証チェック時にリダイレクトさせるときの注意点

2024/07/31に公開

NextAuth.jsを使ってログインチェック時に、ログインしていなかったら(セッションが存在していなかったら)サインイン画面にリダイレクトさせるように実装していました。

このとき、APIモックにmswを使っていたのですが、SSR時に以下のエラーが発生して、かなり詰まったので、ここに共有させていただきます。

環境

使用ライブラリ・フレームワーク バージョン
react 18
next 14.0.4
msw 2.3.4
next-auth 4.24.7

問題

要件として、ログインをしていない場合、サインイン画面にリダイレクトを行う、というものがあります。

リダイレクトされた後の画面に遷移すると、以下のエラーが表示されてしまいました(調査の結果、リダイレクトされない画面にアクセスしても、やはり以下のエラーが発生しました)。

また、モックとしてmswを導入しており、mswでAPI通信をinterceptしていました。

msw+next-auth.js_redirect

前提

まず、未ログイン時のリダイレクト実装は、NextAuth.jsを用いて、以下のように実装していました。

// /src/middleware.ts

import { withAuth } from 'next-auth/middleware';  

export default withAuth({
    callbacks: {
        authorized: ({ token }) => !!token,
    },
    pages: {
        signIn: '/signin',
    },
});

export const config = {
    matcher: ['/((?!signin).*)'],
};

公式ドキュメントにあるように、API routeにも以下の実装を行なっています。

// /src/pages/api/auth/[...nextauth].ts

import nextAuth, { AuthOptions } from 'next-auth';
import GithubProvider from 'next-auth/providers/github';  

export const authOptions: AuthOptions = {
    providers: [
        GithubProvider({
            clientId: process.env.GITHUB_CLIENT_ID || '',
            clientSecret: process.env.GITHUB_CLIENT_SECRET || '',
        }),
    ],
    session: { strategy: 'jwt' },
    callbacks: {
        redirect: async ({ baseUrl }) => {
            return baseUrl;
        },
    },
};  

export default nextAuth(authOptions);

API通信をinterceptしてモックを通すために、mswも導入しています。
詳細は避けますが、Service Worker or Node Serverの起動を以下のように実装しています。

// /src/mock/index.ts

import { SharedOptions } from 'msw';

async function initMocks() {
    const options: Partial<SharedOptions> | undefined = {
        onUnhandledRequest: 'bypass',
    };

    if (typeof window === 'undefined') {
        const { server } = await import('~/mock/server');
        server.listen(options);
    } else {
        const { worker } = await import('~/mock/browser');
        worker.start(options);
    }
}

そして、このモックを、nextのエンドポイントファイルに以下のように実装していました。

// /src/pages/_app.tsx

import { AppProps } from 'next/app';

import { SessionProvider } from 'next-auth/react';
import { Provider as StateProvider } from 'react-redux';
import reset from 'sanitize.css';
import { createGlobalStyle } from 'styled-components';
import { initMocks } from '~/mock';

import Layout from '~/components/container/Layout/Layout';
import Toast from '~/components/container/Toast/Toast';

import { useAppWrappedStore } from '~/hooks/useRedux';

if (
    process.env.NODE_ENV === 'development' &&
    process.env.NEXT_PUBLIC_USE_MOCK === 'true'
) {
    initMocks();
}

export default function App({
    Component,
    pageProps: { session, ...pageProps },
}: AppProps) {
    const { store, props } = useAppWrappedStore(pageProps);

    ~~~ 以下関係無いので省略 ~~~

原因

エラーを見ると、

mockServiceWorker.js`のスクリプトリソースはリダイレクトの前にあり、許可されていない。

と記載されています(DeepL翻訳)。これを読んでもあまり意味が分からないのですが、何かリダイレクトが関係していそう?、と考えました。

リダイレクトに関しては、NextAuth.jsによるセッションチェック時に、セッションが存在していなかったらリダイレクトを行う、という処理を行なっていました。

ここから長く悩むのですが、エラーの内容をもう一度解釈し直し、「mockServiceWorker.jsはリダイレクトの後、読み込む許可がされていない」と考えました。
もうちょっと噛み砕いて解釈すると、「リダイレクト後にmockServiceWorker.jsを読み込むことができない?」と考えました。

正しいディレクトリにmockServiceWorker.jsを配置しているので読み込むことができないのはおかしいし、そもそもmockServiceWorker.jsはリダイレクトの必要は無いよな?と考えていましたが、そういえばセッションチェック時のURLマッチャーの設定を/signin以外のパスに設定しているのを思い出しました。

ここで、http://localhost:3000/mockServiceWorker.jsにアクセスするときも、NextAuth.jsのセッションチェックによるリダイレクト処理が入ってしまっていることが分かりました。

案の定、http://localhost:3000/mockServiceWorker.jsに直接アクセスすると、リダイレクトが走っているのが確認できました。

解決

というわけで、リダイレクト処理のマッチャーを修正しました。

import { withAuth } from 'next-auth/middleware';

export default withAuth({
    callbacks: {
        authorized: ({ token }) => !!token,
    },
    pages: {
        signIn: '/signin',
    },
});

export const config = {
    matcher: ['/((?!signin|mockServiceWorker.js).*)'],  // ここを修正 
};

参考

https://next-auth.js.org/configuration/nextjs#middleware

転載元

https://kostum.hatenablog.jp/entry/2024/07/27/100000

Discussion