🌊

laravel next.js sanctum SPA認証 

2024/04/02に公開

バック laravel
フロント next.js
で認証機能を実装する際に躓いたのでメモとして記録に残します。

今回の原因と結論

結論から言うと、なんとか解決できました。

SANCTUM_STATEFUL_DOMAINS
SESSION_DOMAIの設定を誤っていた。

Kernel.phpの設定を誤っていた。
この2点が原因でした。

本文

ログインページのコードを下記に記載します。

Login.tsx
"use client";
import React, { useContext } from 'react';
import { Button, Input, PasswordInput } from '@mantine/core';
import axios from 'axios';
import { useRouter } from 'next/navigation';

const http = axios.create({
    baseURL: 'http://localhost',
    withCredentials: true,
    withXSRFToken: true,
});

const Login = () => {
    const [email, setEmail] = React.useState('');
    const [password, setPassword] = React.useState('');
    const router = useRouter();
    const [accessToken, setAccessToken] = React.useState('');

    const postData = async () => {
        http.get('/sanctum/csrf-cookie').then((res: any) => {
            console.log(res);
            console.log("CSRF Cookie set successfully.");
            // ログイン処理
            http.post('/api/login', { email, password }).then((res) => {
                console.log(res.data);//トークンの値
                const accessToken = res.data.access_token;
                setAccessToken(accessToken); // トークンをセット
                console.log("Access Token:", accessToken);
                alert('ログインに成功しました!');
                router.push('/mypage');
            }).catch((error) => {
                console.error("Login failed:", error);
            });
        });
    };

    const getUserInfo = async () => {
      try {
          const response = await http.get('/api/users/me', {
          });
          console.log('User Info:', response.data);
      } catch (error) {
          console.error('Failed to fetch user info:', error);
      }
  };



    const postlogin = async () => {
      try {
        const response = await http.post('/api/login', { email, password });
        console.log(response.data);//トークンの値
        const accessToken = response.data.access_token;
        console.log("Access Token:", accessToken); 
      } catch (error) {
        console.error("Login failed:", error);
      }
    };


  

    return (
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
            <h2>ログインページ</h2>
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '20vh', border: '1px solid #ccc', borderRadius: '5px', padding: '3%', marginBottom: '2%' }}>
                <div style={{ display: 'grid', margin: '1%' }}>
                    <label htmlFor="email">Email</label>
                    <Input
                        placeholder="Email"
                        style={{ width: '300px' }}
                        value={email}
                        onChange={(e) => setEmail(e.target.value)}
                    />
                </div>

                <div style={{ display: 'grid', margin: '1%' }}>
                    <label htmlFor="password">Password</label>
                    <PasswordInput
                        placeholder="Password"
                        style={{ width: '300px' }}
                        value={password}
                        onChange={(e) => setPassword(e.target.value)}
                    />
                </div>
            </div>

            <Button onClick={postData}>ログイン</Button>
            <Button onClick={getUserInfo}>ユーザー情報取得</Button>
            <Button onClick={postlogin}>ログイン処理のみ</Button>
        </div>
    );
};

export default Login;

画面はこのようになっています。
デバックのため
ログイン処理してマイページ画面に遷移するための ログインボタン
ログイン処理だけする ログイン処理のみボタン
/api/users/meでユーザー情報を取得するためだけの ユーザー情報取得ボタン
の3つを作っています。

問題1 corsエラー


上記のエラーが表示されました。
cors.phpのファイルは下記になります。

cors.php
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Cross-Origin Resource Sharing (CORS) Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may configure your settings for cross-origin resource sharing
    | or "CORS". This determines what cross-origin operations may execute
    | in web browsers. You are free to adjust these settings as needed.
    |
    | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
    |
    */
    //対象にするルーティング 例: ['api/*', 'sanctum/csrf-cookie']
    'paths' => ['*'],

    //許可する通信GETのみ許可する場合: ['GET']
    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    // trueに変更する
    'supports_credentials' => true,

];

このように設定していました。
実際の運用の際はよくない設定ですが、pathもallowed_methodsもワイルドカードを指定しているので問題ないはずなのに...
原因は
corsで弾かれたので調べてみた時に入れた下記の記述が原因でした。
\Illuminate\Http\Middleware\HandleCors::class,
と言う記述もあったのでこちらとぶつかってしまったのかなと思います。
下記の行をコメントアウトすることによって問題は解決できました。
原因はわかり次第追記します。
とりあえず動いたのでよかった。

Kenrl.phpの

    protected $middleware = [
        \Fruitcake\Cors\HandleCors::class,
    ];

問題2 ドメインの指定ミス

次に
躓いたエラーは
next側で

  • get('/sanctum/csrf-cookie')を実行後
  • post('/api/login', { email, password })でログインを試みる。
  • get('/api/users/me')で現在のログイン中のユーザの情報を取得。

ログイン処理のみボタンを押しその後ユーザー情報取得ボタンを押した際に下記エラーが発生しました。

api/users/meにget通信した際にエラーが返ってきています。
ここでapi.phpのファイルを見てみます。

api.php
Route::middleware('auth:sanctum')->get('/users/me', function (Request $request) {
    Log::info("logtest");
    Log::info('Request data:', $request->all());
    return $request->user();
});

このファイルの
middleware('auth:sanctum')これの認証部分で弾かれていると仮説を立てて下記に書き換えてみました。

api.php
Route::get('/users/me', function (Request $request) {
    Log::info("logtest");
    Log::info('Request data:', $request->all());
    return $request->user();
});

変更後実行した結果
User Info:
と値が帰ってきました。

middleware('auth:sanctum')の認証で弾かれていると言うことと思います。

.envファイルの
SANCTUM_STATEFUL_DOMAINSにはフロントのドメイン
SESSION_DOMAINにはバックのドメイン
を記載すると言う記事を見つけました。
.envファイルを2つの値を逆に設定していたのが原因でした。

最終的に動かしてみた結果は下記になります。
corsの問題もapi/users/meの部分でもエラーなく期待通りの動きになりました。

ログイン処理のみとユーザー情報取得を実行しました。

users/meを利用することで毎回必要な値を渡す手間がなくなって便利でした。

Discussion