LoginSignup
1
2

laravel 10 + JWT +Reactで承認API(メール認証付き)を実装する

Last updated at Posted at 2023-06-24

SPAで承認機能を作成する。

以下を使って実装する

・サーバサイド:Laravel 10
・Larabelの承認機能:Laravel UI
・フロントエンド:React

開発環境:mac/mac OS Monterey ver.12.3.1

【参考】
↓↓JWTのインストール↓↓
https://www.laravelia.com/post/laravel-10-jwt-complete-api-authentication-tutorial

Laravel 10のプロジェクトのビルドとRactの導入はこちらを参考に。

Laravelに承認機能を導入する

Laravel 10で承認機能を利用するには、Laravel uiやBreezeなど使う手段があるが、
今回はLaravel uiを使用する。

まずはLaravel UIパッケージのインストール。

ターミナル
composer require laravel/ui

認証のScaffolding(骨組み)を作成するためにphp artisan uiコマンドを実行。
今回はReactを利用するのでreactを指定する。

ターミナル
php artisan ui react --auth

DBの設定とマイグレーション

.envファイルを修正し、sqliteを使用するように中身を書き換える

//〜〜中略〜〜

+ DB_CONNECTION=sqlite //sqliteに変更
//以下全て削除する
- DB_HOST=127.0.0.1
- DB_PORT=3306
- DB_DATABASE=laravel
- DB_USERNAME=root
- DB_PASSWORD=

//〜〜中略〜〜

マイグレートを実行する

ターミナル
php artisan migrate

コマンドを実行すると、下記のように問われるがyesにする。
(yesにすることで、databaseディレクトリにdatabase.sqliteファイルが作成される)

  WARN  The SQLite database does not exist: database/database.sqlite.  

  Would you like to create it? (yes/no) [no]

JWTのインストール

下記のコマンドでJWTをインストール

ターミナル
composer require php-open-source-saver/jwt-auth

続いて、コンフィグレーションファイルを発行する

ターミナル
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"

これを実行することで、configディレクトリにjwt.phpというファイルが作成される。

JWTシークレットキーを下記コマンドで作成する

コマンド
php artisan jwt:secret

API承認ガードの設定

API承認ガードの設定を行う。configディレクトリのauth.phpを以下のように修正していく

auth.php



return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
-       'guard' => 'web',
+       'guard' => 'api',  //apiに変更
        'passwords' => 'users',
    ],

    //〜〜中略〜〜

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

+ //追記する
+        'api' => [
+            'driver' => 'jwt',
+            'provider' => 'users',
+       ],
    ],

    //〜〜中略〜〜

];

Userモデルの編集

JWTを利用できるようにUserモデルを修正する。
ModelsディレクトリのUser.phpを修正する。

User.php
<?php

namespace App\Models;

- // use Illuminate\Contracts\Auth\MustVerifyEmail;
+ use Illuminate\Contracts\Auth\MustVerifyEmail; //コメントアウトされてたら戻す
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
+ use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject; //追記

- class User extends Authenticatable
+ class User extends Authenticatable implements JWTSubject //implements JWTSubjectを追記
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];

+    //追記
+    public function getJWTIdentifier()
+    {
+        return $this->getKey();
+    }

+   //追記
+   public function getJWTCustomClaims()
+    {
+        return [];
+    }
    
}

ルートの確認

authの機能を導入によりweb.phpに勝手にルートが追加される。
不要な部分を削除もしくはコメントアウトする。

web.php
<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/


// ↓もとから書かれてたコード。削除かコメントアウトする。
// Route::get('/', function () {
//     return view('welcome');
// });

// これだけを書いておく
Route::get('{any}', function () {
    return view('app');
})->where('any','.*');

- //↓削除
- Auth::routes();
- //↓削除
- Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

ユーザー新規登録APIの実装

Laravelで新規ユーザーのデータをDBに追加するためのコントローラを作成する。
コマンドで作成する。今回は、"RegisterController"とう名前で作成する。

ターミナル
php artisan make:controller RegisterController

app/Http/ControllersディレクトリにRegisterController.phpが作成される。
この中身を、以下のように編集する。

RegisterController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Models\User;

class RegisterController extends Controller
{
    //
    public function register(Request $request){
        $validator = Validator::make($request->only(["name", "email", "password", "password_confirmation"]), [
            'name' => 'required|max:10|unique:users',
            'email' => 'required|email|unique:users|max:50',
            'password' => 'required|confirmed|string|min:6|max:30',
        ]);

        if ($validator->fails()) {
            return response()->json($validator->errors(), 422);
        }

        $user = User::create(array_merge(
            $validator->validated(),
            ['password' => bcrypt($request->password)]
        ))->sendEmailVerificationNotification();

        return response()->json([
            'message' => '登録しました',
            'email' => $request->email
        ], 201);
    }
}

続いて、ルートを設定する。routesディレクトリのapi.phpを下記のように編集する。
また、ポイントとして、"->name('verification.verify')"を付けること。
これを忘れるとエラーになる。

api.phpを編集しコントローラをルートに設定する。

api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
+ use App\Http\Controllers\RegisterController; //追加

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/


- Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
+ Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
 });

+ //追加
+ Route::middleware(['api'])->group(function ($router){
+    Route::post('/register',[RegisterController::class,'register'])->name('verification.verify');
+ });

登録後、確認メールを送るのでメールが送れるように設定する。
.envファイルの下記部分を自分用の設定に編集する。SMTP サーバーの導入や設定の説明はここではしない。

.env
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

これで、新規登録のAPIの実装は完了。

React側でのビューの例はこんな感じ。
※cssはtailwind cssを使用。cssに関する説明は今回のメインではないので省く。

RegisterForm.jsx
import React, { useState} from 'react';
import axios from 'axios';


function RegisterForm(props){

    const [registerInput, setRegister] = useState({
        name: '',
        email: '',
        password: '',
        password_confirmation: '',
    });

    const [responseData, setResponse] = useState({
        message: '',
        email: '',
        error_name: '',
        error_email: '',
        error_password: '',
    });

    const [ isLoading, setIsLoading ] = useState(false);


    const handleInput = (e) => {

        //イベントハンドラが実行された後にオブジェクトのプロパティにアクセスする必要がある場合は、e.persist() を呼ぶ必要がある
        e.persist();

        setRegister({...registerInput, [e.target.name]: e.target.value });
    }

    const registerSubmit = (e) => {

        //フォームデータ送信時に画面を再更新しないようにする処理
        e.preventDefault();
        setIsLoading(true);
        

        const data = {
            name: registerInput.name,
            email: registerInput.email,
            password: registerInput.password,
            password_confirmation: registerInput.password_confirmation,
        }


        axios.post('http://127.0.0.1:8000/api/register', data).then(function (response) {
            // 送信成功時の処理
            setIsLoading(false);
            console.log(response.data);
            setResponse({
                message: response.data.message,
                email: response.data.email,
            });

            props.setRegisterCheck({
                status: false,
                email: registerInput.email,
            });
            
        })
        .catch(function (error) {
            // 送信失敗時の処理
            alert('NG');
            console.log('通信に失敗しました');
            setResponse({
                error_name: error.response.data.name,
                error_email: error.response.data.email,
                error_password: error.response.data.password,
            });
                
        });
    }


    return (
        <div className="w-96 ml-auto mr-auto">
            
            <div className="block w-full mt-10 mb-10 p-2 rounded-3xl bg-slate-200">
            <h1 className="w-full border-b-2 text-center text-2xl mt-10 mb-10 font-bold">新規登録</h1>

            <div>
                { isLoading ? '送信中....' : '' }
            </div>     

            <form onSubmit={registerSubmit}>

                <span className="block w-full mt-4">名前</span>
                <span className="block text-red-500">{responseData.error_name}</span>
                <input type="text" name="name" 
                    value={registerInput.name} 
                    onChange={handleInput} 
                    className="block w-full h-10 border border-gray-600 rounded"
                />

                <span className="block w-full mt-4">メールアドレス</span>
                <span className="block text-red-500">{responseData.error_email}</span>
                <input type="email" name="email" 
                    value={registerInput.email}  
                    onChange={handleInput} 
                    className="block w-full h-10 border border-gray-600 rounded"
                />

                <span className="block w-full mt-4">パスワード</span>
                <span className="block text-rose-600">{responseData.error_password}</span>
                <input type="password" name="password" 
                    value={registerInput.password}  
                    onChange={handleInput} 
                    className="block w-full h-10 border border-gray-600 rounded"
                />

                <span className="block w-full mt-4">パスワード(確認でもう一度入力)</span>
                <input type="password" name="password_confirmation" 
                    value={registerInput.password_confirmation}  
                    onChange={handleInput} 
                    className="block w-full h-10 border border-gray-600 rounded"
                />

                <button type="submit" 
                    className="block mt-10 mb-10 bg-amber-500 w-full h-10 text-white ml-auto mr-auto rounded-lg shadow-lg font-medium text-1xl">
                    登録
                </button>

            </form>       
            </div>
        </div>
    );
    
    
}


export default RegisterForm;

ユーザー新規登録メール承認APIの実装

先ほどのユーザー新規登録APIでは、ユーザー登録処理した後に、
登録ユーザーのメールアドレスに承認用のメールが届くようになっている。
このメールの承認ボタンもしくはリンクを押すことでユーザーのアカウントが有効にする
APIを実装する。RgisterVerifyControllerを作成する。

ターミナル
php artisan make:controller RgisterVerifyController

作成できたら、以下のように編集する。

RgisterVerifyController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;


class RgisterVerifyController extends Controller
{
    //
    public function verify($user_id, Request $request){

        //送信されてきたリクエストが有効な著名を持っているかを検査
        if(!$request->hasValidSignature()){
            //特定のページに(例えばホームなど)に戻す。自由にURLを適時カスマイズする。
            return redirect()->to('/');
        }

        $user = User::findOrFail($user_id);

        if(!$user->hasVerifiedEmail()){
            //markEmailAsVerified()でUserテーブルの"email_verifiyed_at"に日付を保存してる?
            $user->markEmailAsVerified();
        }

        //メール認証後に特定のページに移動。自由にURLを適時カスマイズする。
        return redirect()->to('/');
    }


    public function resend(Request $request){

        $user = User::where('email','=',$request->email)->first();
        $user->sendEmailVerificationNotification();
        return response()->json(['message' => 'メール承認のリンクを再送しました']);
    }
}

api.phpを編集しルートにコントローラを設定

api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RegisterController;
+ use App\Http\Controllers\RgisterVerifyController;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/


 
 Route::middleware(['api'])->group(function ($router){
    //ユーザー新規登録
    Route::post('/register',[RegisterController::class,'register'])
    ->name('verification.verify');

    //メール承認処理(アカウント有効化処理)  
+   Route::get('email/verify/{id}',[RgisterVerifyController::class,'verify'])
+   ->name('verification.verify');

    //承認メールの再送信処理
+   Route::get('email/resesnd',[RgisterVerifyController::class,'resend'])
+   ->name('verification.resend');
 });

    

Reactのビューで承認メール送信確認&再送信の画面を作る。
例としてはこんな感じで。

RegisterEmailVerify.jsx
import axios from 'axios';

document.title = 'メール承認';

function RegisterEmailVerify(props) {

  function Submit(e) {

    e.preventDefault();

    axios.get('http://127.0.0.1:8000/api/email/resesnd', {params: {email: props.email}}).then(function (response) {
        // 送信成功時の処理
        alert('メールを再送しました');
    })
    .catch(function (error) {
        // 送信失敗時の処理
        alert('NG');
        console.log('通信に失敗しました');
    });

  }


  return (
    <div className="w-full mt-20 mb-20">
        <h1 className="w-full border-b-2 text-center text-2xl mb-10">
          メール承認
        </h1>
        
        <p>
          <b>{props.email}</b>宛にメールを送りました。<br/>
          届いたメールの承認ボタンかリンクよりアカウントを有効化ください。
          
        </p>
        <form onSubmit={Submit}>
          <button type="submit" className='text-yellow-500'>
            メールが届いてない場合はこちらをクリック
          </button>
        </form>
    </div>
  );
}

export default RegisterEmailVerify;

このメール承認確認画面のRegisterEmailVerifyコンポーネントと
先ほど作った登録フォーム画面コンポーネントを、登録して承認メールが正常に送れたら
登録フォーム画面→メール承認確認画面というように表示を切り替えするような仕掛けにしたい。
そこで、Registerコンポーネントを作成し、そこで表示を切り替える仕組みにする。

Register.jsx
import React, { useState, useEffect} from 'react';
import RegisterEmailVerify from './RegisterEmailVerify';
import RegisterForm from './RegisterForm';



function Register(){

    // 初期状態(status=true)の場合、登録情報の入力画面コンポーネントを表示する
    const [ registerCheck, setRegisterCheck ] = useState({
        status: true,
        email: '',
    });


    return (
        // 登録情報の送信が完了or未了で表示を切り替える
        <div className="w-1/3 ml-auto mr-auto">
            
            { registerCheck.status ? 
                <RegisterForm setRegisterCheck={setRegisterCheck} /> 
                : 
                <RegisterEmailVerify email={registerCheck.email} /> 
            }
        
        </div>
    );
    
    
}

export default Register;

このRegisterコンポーネントではregisterCheckという変数がtrueならば
三項子演算でRegisterFormコンポーネントを表示する。
初期状態でregisterCheck=trueとして登録画面を表示するようにしている。
RegisterFormコンポーネントで登録処理に成功するとprops経由でregisterCheck=falseにして
RegisterEmailVerifyコンポーネントを表示するようにしている。

ログインAPIの実装

LoginControllerを作成する。

ターミナル
$ php artisan make:controller LoginController

また、React側でトークン情報をクッキーで保存するのでreact-cookieをインストールする。

ターミナル
$ npm install react-cookie

インストールでエアラーになる場合
npm install react-cookie --legacy-peer-depsでインストールする

LoginControllerを下記のように編集する

LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Validator;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use JWTAuth;//JWTAuth::attempt()を使用するため

class LoginController extends Controller
{
    //
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login']]);
    }

    /**
     * ログイン処理
     * メール承認が完了(Userテーブルのemail_verified_at != null)していないと
     * ログインができない仕様にしてある
     */
    public function login()
    {
        $credentials = request(['email', 'password']);

        // メール承認のカラムのデータ取得
        $user = Authenticatable::where('email','=',request(['email']))->first();
        $verify_check = $user->email_verified_at;
        $user_name = $user->name;

        // メール承認のカラムがnullどうかチェック。nullならログインを却下する
        if(is_null($verify_check)){
            return response()->json(['error' => 'メールアドレスの承認が完了していません。承認がされていないアカウントではログインできません。'], 401);
        }

        //JWTAuth::attempt()にしないとトークンが得られなかった(auth()->attemptだとtrueと帰ってくる)
        if (! $token = JWTAuth::attempt($credentials)) {
            return response()->json(['error' => 'ログインできません。メールアドレス、パスワードをご確認ください。'], 401);
        }

        return $this->respondWithToken($token,$verify_check,$user_name);
    }

    /**
     * マイページ
     */
    public function me()
    {
        return response()->json(auth()->user());
    }


    /**
     * Refresh a token.
     */
    public function refresh()
    {
        return $this->respondWithToken(auth()->refresh());
    }

    /**
     * ログイン成功時にトークンなどの値を返す
     */
    protected function respondWithToken($token,$verify_check,$user_name)
    {
        return response()->json([
            'access_token' => $token,
            'email_verify' => $verify_check,
            'user_name' => $user_name,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ],200);
    }
}

api.phpにルートを設定

api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RegisterController;
use App\Http\Controllers\RgisterVerifyController;
+ use App\Http\Controllers\LoginController;

/*
    〜〜〜〜中略〜〜〜〜
*/


 
 Route::middleware(['api'])->group(function ($router){
    //ユーザー新規登録
    Route::post('/register',[RegisterController::class,'register'])
    ->name('verification.verify');

    //メール承認処理(アカウント有効化処理)  
    Route::get('email/verify/{id}',[RgisterVerifyController::class,'verify'])
    ->name('verification.verify');

    //承認メールの再送信処理
    Route::get('email/resesnd',[RgisterVerifyController::class,'resend'])
    ->name('verification.resend');

+    //ログイン処理
+    Route::post('/login',[LoginController::class,'login']);
 });

    

ビューのReactコンポーネントの例はこんな感じ

Login.jsx
import React, { useState} from 'react';
import axios from 'axios';
import {Link,useNavigate} from 'react-router-dom';
import { useCookies } from "react-cookie";




function Login() {
    //ページ遷移で使う
    const navigate = useNavigate();


    // フォーム入力の値の変数を定義
    const [loginInput, setLogin] = useState({
        email: '',
        password: '',
    });


    // トークンを保存するクッキーを定義
    const [auth_token, setCookie, removeCookie] = useCookies(["token"]);


    // フォーム入力された値をsetLogin関数で更新
    const handleInput = (e) => {

        //イベントハンドラが実行された後にオブジェクトのプロパティにアクセスする必要がある場合は、e.persist() を呼ぶ必要がある
        e.persist();

        setLogin({...loginInput, [e.target.name]: e.target.value });
    }

    // フォームのボタンがsubmitされた時に、ログインの処理をする
    const LoginSubmit = (e) =>{

        //フォームデータ送信時に画面を再更新しないようにする処理
        e.preventDefault();

        // axiosで送るデータを定義。ここにフォームで入力された値(email,password)が入る。
        const data = {
            email: loginInput.email,
            password: loginInput.password,
        }

        // axiosでログインAPIにemail,passwordをHTTP通信で送る
        axios.post('http://127.0.0.1:8000/api/login', data).then(function (response) {

            // --------送信成功時の処理-------- //
            alert('ログイン成功');

            //トークンをクッキーに保存
            setCookie("token",response.data.access_token);

            // props.setAuth(true);

            //ユーザ名をローカルストレージに保存
            localStorage.setItem('user_name',response.data.user_name);

            //ステータスコードをローカルストレージに保存
            // localStorage.setItem('status',response.status);
            localStorage.setItem('auth_status',response.status);

            //ログイン後の移動先
            navigate("/");

            
        })
        .catch(function (error) {
        
            // --------送信失敗時の処理-------- //
            alert(error.response.data.error);
            console.log(error);

        });

    }


    return (
        <div className="block w-1/3 ml-auto mr-auto mt-10 mb-10 p-5 rounded-3xl bg-white text-slate-600">
            <h1 className="w-full text-center text-2xl mt-10 mb-10">ログイン</h1>

            <form onSubmit={LoginSubmit}>
                <div>
                    <p>メールアドレス</p>
                    <input type="email" name="email" 
                        value={loginInput.email}  
                        onChange={handleInput} 
                        className="block w-full h-10 border border-gray-600 rounded"
                        placeholder="メールアドレス"
                    />
                </div>
                <div className="mt-10">
                    <p>パスワード</p>
                    <input type="password" name="password" 
                        value={loginInput.password}  
                        onChange={handleInput}
                        className="w-full h-10 border border-gray-600 rounded"
                        placeholder="パスワード"
                    />
                </div>
                <button type="submit" className="block mt-10 bg-amber-400 w-full h-10 text-white ml-auto mr-auto rounded-lg shadow-lg font-medium text-1xl">ログイン</button>
            </form>

            <Link to="/forgot-password" className="">
                <span className="block w-full text-cyan-500 mt-10">パスワードを忘れた</span>
            </Link>

        </div>
    );
    
}

export default Login;

ログインユーザーのマイページの作成とページの承認アクセス制御

api.phpを下記のように編集

php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RegisterController;
use App\Http\Controllers\RgisterVerifyController;
use App\Http\Controllers\LoginController;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:api')->get('/user', function (Request $request) { 
    return $request->user();
});

+ //承認確認
+ Route::middleware('auth:api')->get('/auth', function (Request $request) { 
+     return response()->json(['authenticated' => 200]);
+ });

// 〜〜〜 中略 〜〜〜 

React側で登録ユーザーの情報を表示するビューを作成。

MyPage.jsx
import React from 'react';
import axios from 'axios';
import { Cookies, useCookies } from "react-cookie";
import { useState, useEffect, useContext } from "react";


function MyPage(){
    const [auth_token, setCookie, removeCookie] = useCookies(["token"]);
    const API_TOKEN = auth_token.token 

    const [user ,setUser] = useState({
        name:'',
        email:'',
        password:'',
        created_at:'',
    });

    useEffect(()=>{
        // トークンでアクセスしてユーザー名を取得
        axios.get('http://127.0.0.1:8000/api/user',{ headers: { Authorization: "Bearer " + API_TOKEN } }).then((response) => { 
            console.log(response);
            
            setUser({
                name:response.data.name,
                email:response.data.email,
                password:response.data.password,
                created_at:response.data.created_at,                
            });

        }).catch((error) => { 
            
            console.log(error.response.status);
            localStorage.setItem('auth_status',error.response.status);
            localStorage.setItem('isAuth',false);
        });
    },[]);

    return(
        <div className="block w-1/3 ml-auto mr-auto mt-10 mb-10 p-5 rounded-3xl bg-white text-slate-600">
            <h1 className="text-3xl">MyPage</h1>
            <ul>
                <li>{user.name}</li>
                <li>{user.email}</li>
                <li>{user.password}</li>
                <li>{user.created_at}</li>
            </ul>
        </div>
    );
}

export default MyPage;

このページは、ログインしたユーザーにしか表示させたくない。
reactでルート制御を行うため、AuthRequier.jsxを作成し、下記のように編集する。

AuthRequier.jsx
// ログインユーザーのみアクセスできる制限をかける関数

import { useState, useEffect } from 'react';
import axios from 'axios';
import { Cookies, useCookies } from "react-cookie";
import { Navigate } from 'react-router-dom';

/**  
* ログイン処理の際に、ローカルストレージに200ステータスを保存している。
* cookieに承認トークンを保存しているので、このトークンで承認成功すれば、ログインユーザーのみページのアクセスを許可する。
* トークンでの承認が失敗した場合、ローカルストレージのステータスを401に上書きしてページのアクセスを拒否する。
*/

const AuthRequier = ({ children }) => {

  const [auth_token, setCookie, removeCookie] = useCookies(["token"]);
  const API_TOKEN = auth_token.token;

  useEffect(()=>{
      // cookieに保存されたトークンでauth承認APIにアクセスし、失敗(トークン有効期限切れ)の場合ローカルストレージのステータスを401に上書きする。
      axios.get('http://127.0.0.1:8000/api/auth',{ headers: { Authorization: "Bearer " + API_TOKEN } }).catch((error) => { 
        localstorage.setItem('auth_status', 401);
      });
  },[]);

  const isAuthenticated = localStorage.getItem('auth_status');

  //未承認ユーザーがアクセスした場合はログインページに移動させる
  return isAuthenticated == 200 ? (children) : ( <Navigate to="/login" />);

};

export default AuthRequier;

ルートはこのように書く

app.jsx
/**
 * First we will load all of this project's JavaScript dependencies which
 * includes React and other helpers. It's a great starting point while
 * building robust, powerful web applications using React + Laravel.
 */

import './bootstrap';

/**
 * Next, we will create a fresh React component instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */


+ //承認ユーザーのみアクセス可能
+ import AuthRequier from './components/auth_components/AuthRequier';

import ReactDOM from "react-dom/client";
import { render } from 'react-dom';
import { BrowserRouter, Routes, Route, Navigate} from 'react-router-dom';



const root = ReactDOM.createRoot(document.getElementById("app"));
root.render(
    <>
        <BrowserRouter>
            <Routes>
               // 〜〜〜 中略 〜〜〜 
+              <Route path="/mypage" element={<AuthRequier><MyPage /></AuthRequier>} />
               // 〜〜〜 中略 〜〜〜 
            </Routes>
            <Footer />
        </BrowserRouter>
    </>
);

これで、承認ユーザーのアクセス制御が可能になる。

ログアウトAPIの実装

ログアウトのAPIでの処理は非常に簡単で、下記の処理で可能。

    /**
     * ログアウト処理
     */
    public function logout()
    {
        auth('')->logout();

        return response()->json(['message' => '正常にログアウトしました。']);
    }

コード量が少ないので、じぶんはLoginControllerにログアウト処理を書いた。
(もちろん新規にログアウト用にコントローラを作成してこのコードを書いてもOK)

api.phpにログアウトのルートを設定

php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RegisterController;
use App\Http\Controllers\RgisterVerifyController;
use App\Http\Controllers\LoginController;

// 〜〜〜 中略 〜〜〜

Route::middleware('auth:api')->get('/user', function (Request $request) { 
    return $request->user();
});

// 〜〜〜 中略 〜〜〜

+ Route::middleware(['jwt.auth'])->group(function (){
+    Route::post('logout', 'App\Http\Controllers\LoginController@logout')->name('logout');
+ });

ここまでで、laravel 10 + JWT +Reactでの新規登録・ログイン・ログアウトのAPIが完成。

1
2
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
1
2