Back to Course |
Vue.js 3 + Laravel 11 + Vite: SPA CRUD

Login Form and First Authentication

In this lesson, we will implement the login form and authenticate the user.

login validation error


Login form

So, first the login form.

resources/js/components/Auth/Login.vue:

<template>
<form @submit.prevent="submitLogin">
<!-- Email -->
<div>
<label for="email" class="block font-medium text-sm text-gray-700">
Email
</label>
<input v-model="loginForm.email" id="email" type="email" class="block mt-1 w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" required autofocus autocomplete="username">
<!-- Validation Errors -->
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.email">
{{ message }}
</div>
</div>
</div>
 
<!-- Password -->
<div class="mt-4">
<label for="password" class="block font-medium text-sm text-gray-700">
Password
</label>
<input v-model="loginForm.password" id="password" type="password" class="block mt-1 w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" required autocomplete="current-password">
<!-- Validation Errors -->
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.password">
{{ message }}
</div>
</div>
</div>
 
<!-- Remember me -->
<div class="block mt-4">
<label class="flex items-center">
<input type="checkbox" name="remember" v-model="loginForm.remember" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
<span class="ml-2 text-sm text-gray-600">Remember me</span>
</label>
</div>
 
<!-- Buttons -->
<div class="flex items-center justify-end mt-4">
<button class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray transition ease-in-out duration-150 ml-4"
:class="{ 'opacity-25': processing }"
:disabled="processing"
>
</div>
</form>
</template>
 
<script setup>
import useAuth from '@/composables/auth';
 
const { loginForm, validationErrors, processing, submitLogin } = useAuth()
</script>

Nothing special about the form itself. When submitted, we will call the submitLogin method, and all fields are binded using the v-model directive.

Now we need to create a new Composable for Auth and implement all the authentication login in it.

resources/js/composables/auth.js:

import { ref, reactive } from 'vue'
import { useRouter } from "vue-router";
 
export default function useAuth() {
const processing = ref(false)
const validationErrors = ref({})
const router = useRouter()
const loginForm = reactive({
email: '',
password: '',
remember: false
})
 
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 = (response) => {
localStorage.setItem('loggedIn', JSON.stringify(true))
router.push({ name: 'posts.index' })
}
 
return { loginForm, validationErrors, processing, submitLogin }
}

So, what do we do here? The structure and the variables are the same as we have in posts Composable except the loginForm where we bind it to the input in the form with the v-model.

Next, the submit action submitLogin. The processing indicator and the validation errors implementation are the same as we did for the posts form. The only difference here after successful authentication we call the loginUser method.

In the loginUser method we set the loggedIn variable in the local storage to true and redirect to the posts list page.


Login on the Back-end

Now, we need to implement the backend part. For this, we will modify controller that came from the Laravel Breeze.

app/Http/Controllers/Auth/AuthenticatedSessionController.php:

use Symfony\Component\HttpFoundation\JsonResponse;
 
class AuthenticatedSessionController extends Controller
{
// ...
public function store(LoginRequest $request): RedirectResponse
public function store(LoginRequest $request): RedirectResponse|JsonResponse
{
$request->authenticate();
 
$request->session()->regenerate();
 
if ($request->wantsJson()) {
return response()->json($request->user());
}
 
return redirect()->intended(route('dashboard', absolute: false));
return redirect()->intended();
}
// ...
}

In the controller, we check if the request wants JSON which means it comes from Vue.js. If so, we return the user as a JSON.

And we need to add the /login route to the routes file.

routes/web.php:

Route::post('login', [\App\Http\Controllers\Auth\AuthenticatedSessionController::class, 'store']);
 
Route::view('/{any?}', 'dashboard')
->where('any', '.*');

Now if you try to log in with bad credentials, you should see a validation message.

login validation error

But with the correct credentials, you should be redirected to the posts list page!