Using both SPA and Blade view pages during authentication

19 Views Asked by At

Here is my challenge

I have implemented a Single Page Application using React and Laravel Sanctum for API authentication which works perfectly to the requirements and flow desired However, my admin dashboard is in normal blade views and livewires

I am using OTP verification and not email / password combination to authenticate the user and returns the auth()->user()createToken(config('app.name') . ' Personal Access Client')->plainTextToken that works magnificently to function with the auth:sanctum API routes

Unfortunately, the admin Dashboard uses the normal auth middleware to access the blade view pages. In this case, if I try accessing /admin I am redirected back and upon debugging, I noticed auth()->user() is null

How can I use the middleware auth while still retaining the catch-all route structure in the web.php because I know the auth()->user() is null because middleware auth is not captured

I really don't want to use the option of converting the SPA into blade views just because I did not find a solution to the auth problem

Here is web.php

Route::get('{any}', static function () { return view('home.index'); })->where('any', '^(?!api|admin|horizon|telescope).*?');

Route::get('/admin', [DashboardController::class, 'index'])->middleware(['auth', 'role:admin'])->name('dashboard');

Here is home.index -> home/index.blade.php blade view that holds the Single Page Application

<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">

<meta name="user-id" content="@if (auth()->check()) {{ auth()->user()->uid }} @else null @endif" />

<title>{{ config('app.name', 'Laravel') }} | @yield('title')</title>

<!-- Include SweetAlert CSS and JS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js"></script>

<!-- Scripts -->
@viteReactRefresh
@vite(['resources/sass/app.scss', 'resources/js/app.js'])

@laravelPWA

</head>

<body>
    <div id="app">
        @yield('content')
    </div>
</body>

</html>

And here is the LoginVerifyModal.jsx component

import React, { createRef, useState } from 'react';

import axiosClient from "../../route/axios-client";
import { useForm } from "../../control/hooks/useForm";
import { useStateContext } from "../../control/context/ContextProvider";

import ClipLoader from 'react-spinners/ClipLoader';

const LoginVerifyModal = ({ isOpen, onClose, phoneNumber }) => {
    const { notification, setUser, setToken, setNotification } = useStateContext()
    const { errors, setErrors, renderFieldError, message, setMessage, navigate } = useForm()
    const [ waitResponse, setWaitResponse ] = useState(false)

    const codeRef = createRef()

    const onSubmit = async (event) => {
        event.preventDefault()

        setErrors(null);
        setMessage('');

        const code = codeRef.current.value.trim();

        // Validate the code as a 6-digit number
        const codePattern = /^\d{6}$/;
        if (!codePattern.test(code)) {
            Swal.fire({
                title: 'Warning!',
                text: 'Verification code must be a 6-digit number',
                icon: 'warning',
            });
            return;
        }

        const payload = {
            code: code,
        }

        setWaitResponse(true)
        await axiosClient.post('/verify', payload)
            .then(({data}) => {
                setUser(data.data.user)
                setToken(data.data.token)
                setNotification(`Welcome ${data.data.user.name}`)

                setWaitResponse(false)

                onClose(); // Close the modal after confirming

                // After successful OTP verification and state update
                window.location.reload();
            })
            .catch(({response}) => {
                if (response.data.message) {
                    setMessage(response.data.message)
                }
                if (response.data.errors) {
                    setErrors(response.data.errors)
                }
                setWaitResponse(false)
            })
    }

    if (!isOpen) {
        return null;
    }

    return (
        <div id="loginModal" className={`modal ${isOpen ? 'open' : ''}`}>
            <div className="modal-content">
                <h2 className="header-login">
                    Verify Login
                </h2>

                <form onSubmit={onSubmit}>

                    {waitResponse ? (
                        <div className="d-flex justify-content-center">
                            <ClipLoader color={'#0d6efd'} loading={waitResponse} size={150} />
                        </div>
                    ) : (
                        <>
                            <div className="mb-3">
                                <label htmlFor="code" className="form-label">Verification Code</label>
                                <input ref={codeRef}
                                        type="text"
                                        className={`form-control ${errors && errors.hasOwnProperty('code') ? 'is-invalid' : ''}`}
                                        placeholder="Code sent"
                                        id="code"
                                        aria-describedby="code"/>
                                { renderFieldError('code') &&
                                    <span className="invalid-feedback" role="alert">
                                        <strong>{renderFieldError('code')}</strong>
                                    </span>
                                }
                            </div>
                        </>
                    )}

                    <div className="btn-continue-and-cancel">
                        <button id="cancelLogin" onClick={onClose}>Cancel</button>
                        <button id="confirmLogin" type="submit" disabled={!!waitResponse}>Verify</button>
                    </div>

                </form>

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

export default LoginVerifyModal;

For clarity and context, I shared these snippets to emphasize that even when reloading the page after OTP success, the meta value user-id is still null, meaning the user is still invalidated since I am not accessing a route with auth middleware

NOTE: the catch-all route does not hold any middleware

0

There are 0 best solutions below