Step 01: Setting up auth.js composable like before we did for issues.js with structure and variables
import { ref, reactive, inject } from 'vue' import { useRouter } from "vue-router"; const user = reactive({ name: '', email: '', }) export default function useAuth() { const processing = ref(false) const validationErrors = ref({}) const router = useRouter() const loginForm = reactive({ email: '', password: '', remember: false }) const swal = inject('$swal') const submitLogin = async () => { if (processing.value) return processing.value = true validationErrors.value = {} axios.post('/login', loginForm) .then(async response => { loginUser(response) }) .catch(error => { if (error.response?.data) { validationErrors.value = error.response.data.errors } }) .finally(() => processing.value = false) } const loginUser = async (response) => { user.name = response.data.name user.email = response.data.email localStorage.setItem('loggedIn', JSON.stringify(true)) await router.push({ name: 'issues.index' }) } const getUser = () => { axios.get('/api/user') .then(response => { loginUser(response) }) } return { loginForm, validationErrors, processing, submitLogin, user, getUser } }
As mentioned before structure is almost same like issues composable but only difference is after successful login we use loginUser method to set loggedIn variable in the local storage to true and redirect to the issues list page.
Step 02: Setting up Login.vue from previous post like before we did for Issues/Create.vue with validation, v-model and submitLogin method
<template> <main class="py-4"> <div class="container"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card"> <div class="card-header">Login</div> <div class="card-body"> <form @submit.prevent="submitLogin()"> <div class="mb-3"> <label for="email">Email:</label> <input v-model="loginForm.email" type="text" class="form-control" id="text" placeholder="Enter email" name="email"> <div class="text-danger mt-1"> <div v-for="message in validationErrors?.email"> {{ message }} </div> </div> </div> <div class="mb-3"> <label for="email">Password:</label> <input v-model="loginForm.password" type="password" class="form-control" id="text" placeholder="Enter password" name="password"> <div class="text-danger mt-1"> <div v-for="message in validationErrors?.password"> {{ message }} </div> </div> </div> <button :disabled="processing" type="submit" class="btn btn-primary cursor-not-allowed"> <div v-show="processing" class="float-start spinner-border" style="width: 30px; height:30px;"></div> <span v-if="processing"></span> <span v-else>Login</span> </button> </form> </div> </div> </div> </div> </div> </main> </template> <script setup> import useAuth from '../../composables/auth' const { loginForm, validationErrors, processing, submitLogin } = useAuth() </script>
Step 03: Now in backend we need to add following snippet to process login submission
// LoginRequest under Requests/Auth folder ////////////////////////////////////////// namespace App\Http\Requests\Auth; use Illuminate\Auth\Events\Lockout; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; class LoginRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'email' => ['required', 'string', 'email'], 'password' => ['required', 'string'], ]; } /** * Attempt to authenticate the request's credentials. * * @return void * * @throws \Illuminate\Validation\ValidationException */ public function authenticate() { $this->ensureIsNotRateLimited(); if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { RateLimiter::hit($this->throttleKey()); throw ValidationException::withMessages([ 'email' => trans('auth.failed'), ]); } RateLimiter::clear($this->throttleKey()); } /** * Ensure the login request is not rate limited. * * @return void * * @throws \Illuminate\Validation\ValidationException */ public function ensureIsNotRateLimited() { if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { return; } event(new Lockout($this)); $seconds = RateLimiter::availableIn($this->throttleKey()); throw ValidationException::withMessages([ 'email' => trans('auth.throttle', [ 'seconds' => $seconds, 'minutes' => ceil($seconds / 60), ]), ]); } /** * Get the rate limiting throttle key for the request. * * @return string */ public function throttleKey() { return Str::lower($this->input('email')).'|'.$this->ip(); } } // adding store method creating AuthenticateController.php under Controllers/Auth folder /////////////////////////////////////////////////////////////////////////////////////// namespace App\Http\Controllers\Api\Auth; use App\Http\Controllers\Controller; use App\Http\Requests\Auth\LoginRequest; use App\Providers\RouteServiceProvider; class AuthenticateController extends Controller { public function store(LoginRequest $request): RedirectResponse|JsonResponse { $request->authenticate(); $request->session()->regenerate(); if ($request->wantsJson()) { return response()->json($request->user()); } return redirect()->intended(RouteServiceProvider::HOME); } } // changing routes/web.php to add route for login Route::post('login', [App\Http\Controllers\Auth\AuthenticateController::class, 'store']); ....other routes......Now with try to log in with bad credentials, validation message will be seen. But with the correct credentials, redirection will happen to issues list page.