JWT Authentication in a Laravel and Vue.js Application

L earn how to implement JWT authentication in your Laravel and Vue.js application with this step-by-step guide. Discover how to configure Axios for API requests, secure routes with middleware, create an authentication controller, and build a login component. Enhance your app's security and user experience with these essential techniques.

JWT Authentication in a Laravel and Vue.js Application
Implementing authentication in web applications is crucial for ensuring security and providing a personalized user experience. In this guide, we'll walk through the steps to set up JWT (JSON Web Token) authentication in a Laravel and Vue.js application. We'll cover the backend configuration using Laravel and JWTAuth, and the frontend integration using Vue.js and Axios.

Setting Up Axios for API Requests in Vue.js

To start, we'll configure Axios to handle API requests, including setting the base URL and attaching the JWT token if it exists.

import axios from 'axios';
window.axios = axios;

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.baseURL = ${import.meta.env.VITE_BASE_URL};

// Set the JWT token if available
const token = localStorage.getItem('token');
if (token) {
  window.axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
Securing Routes with Middleware in Laravel
Next, we'll protect specific routes using JWT authentication middleware.
Route::middleware('auth.jwt')->group(function () {
    // Protected routes go here
});

Configuring Laravel Application with Middleware

We configure the Laravel application to include routing and middleware settings.

<?php

namespace App\Http\Middleware;

use Closure;
use Exception;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
class EnsureTokenIsValid
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
       
            try {
                // Parse the token and authenticate the user
                $user = JWTAuth::parseToken()->authenticate();
                $payload = JWTAuth::getPayload();
   
                // Validate the site identifier
                if ($payload['site'] !== env('APP_URL')) {
                    return response()->json(['status' => 'Invalid site identifier'], 401);
                }
            } catch (Exception $e) {
                if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException) {
                    return response()->json(['status' => 'Token is Invalid'], 401);
                } else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException) {
                    return response()->json(['status' => 'Token is Expired'], 401);
                } else if ($e instanceof \Tymon\JWTAuth\Exceptions\JWTException) {
                    return response()->json(['status' => 'Authorization Token not found'], 401);
                } else {
                    return response()->json(['status' => 'Authorization error',
'error' => $e->getMessage()], 401);
                }
            }
   
            return $next($request);

    }
}


use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
           'auth.jwt' => \App\Http\Middleware\EnsureTokenIsValid::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

Creating an AuthController for User Authentication

We create an AuthController to handle user registration, login, token validation, and other related actions.

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Facades\JWTAuth;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $credentials = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);

        if ($credentials) {
            $user = User::create([
                'name' => $request->name,
                'email' => $request->email,
                'password' => Hash::make($request->password),
            ]);
            $token = JWTAuth::customClaims(['site' => env('APP_URL')])->fromUser($user);
            return $this->respondWithToken($token);
        } else {
            return response()->json($credentials, 422);
        }
    }

    public function login(Request $request)
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);
        $credentials = $request->only('email', 'password');
        try {
            if (!$token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'Invalid credentials'], 401);
            }
        } catch (JWTException $e) {
            return response()->json(['error' => 'Could not create token'], 500);
        }
        $token = JWTAuth::customClaims(['site' => env('APP_URL')])->attempt($credentials);
        return $this->respondWithToken($token);
    }

    public function validateToken(Request $request)
    {
        try {
            $user = JWTAuth::parseToken()->authenticate();
            $payload = JWTAuth::getPayload();

            if ($payload['site'] !== env('APP_URL')) {
                return response()->json(['valid' => false, 'error' => 'Invalid site identifier'], 401);
            }

            return response()->json(['valid' => true], 200);
        } catch (JWTException $e) {
            return response()->json(['valid' => false, 'error' => 'Token is invalid or expired'], 401);
        }
    }
    
    public function me()
    {
        $user = JWTAuth::parseToken()->authenticate();
        return response()->json($user);
    }

    public function logout()
    {
        JWTAuth::invalidate(JWTAuth::getToken());
        return response()->json(['message' => 'Successfully logged out']);
    }

    public function refresh()
    {
        return $this->respondWithToken(JWTAuth::refresh());
    }

    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => JWTAuth::factory()->getTTL() * 60,
            'user' => auth()->user(), 
        ]);
    }
}

Building the Login Component in Vue.js

We create a login component to handle user login requests.

<template>
  <div class="row justify-content-center">
    <div class="col-xl-10 col-lg-12 col-md-9">
      <div class="card shadow-sm my-5">
        <div class="card-body p-0">
          <div class="row">
            <div class="col-lg-12">
              <div class="login-form">
                <div class="text-center">
                  <h1 class="h4 text-gray-900 mb-4">Login</h1>
                </div>
                <form @submit.prevent="login">
                  <div class="form-group">
                    <input
                      type="email"
                      v-model="form.email"
                      class="form-control"
                      id="exampleInputEmail"
                      aria-describedby="emailHelp"
                      placeholder="Enter Email Address"
                    />
                    <span v-if="errors.email" class="text-danger">{{ errors.email[0] }}</span>
                  </div>
                  <div class="form-group">
                    <input
                      type="password"
                      v-model="form.password"
                      class="form-control"
                      id="exampleInputPassword"
                      placeholder="Password"
                    />
                    <span v-if="errors.password" class="text-danger">{{ errors.password[0] }}</span>
                  </div>
                  <div class="form-group">
                    <div class="custom-control custom-checkbox small" style="line-height: 1.5rem;">
                      <input type="checkbox" class="custom-control-input" id="customCheck" />
                      <label class="custom-control-label" for="customCheck">Remember Me</label>
                    </div>
                  </div>
                  <div class="form-group">
                    <button :disabled="loading" type="submit" class="btn btn-primary btn-block">
                      <span v-if="loading">Processing...</span>
                      <span v-else>Login</span>
                    </button>
                  </div>
                  <div v-if="errorMessage" class="alert alert-danger" role="alert">
                    {{ errorMessage }}
                  </div>
                </form>
                <div class="text-center">
                  <RouterLink class="font-weight-bold small" to="/register">Create an Account!</RouterLink>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import axios from 'axios';
import { RouterLink } from 'vue-router';

export default {
  name: 'Loginpage',
  data() {
    return {
      form: {
        email: '',
        password: '',
      },
      successMessage: '',
      errorMessage: '',
      errors: {},
      loading: false,
    };
  },
  methods: {
    async login() {
      try {
        this.loading = true;
        const response = await axios.post('/api/auth/login', this.form);

        if (response.status === 200) {
          // Store the token and user name in localStorage
          const token = response.data.access_token;
          localStorage.setItem('token', token);
          localStorage.setItem('user_name', response.data.user.name);

          Toast.fire({
            icon: 'success',
            title: 'Signed in successfully',
          });

          // Update Axios headers
          axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;

          // Redirect to dashboard or next page
          this.$router.push('/dashboard');
          this.errorMessage = '';
          this.errors = {};

          // Clear the form
          this.form.email = '';
          this.form.password = '';
        }
      } catch (error) {
        if (error.response && error.response.status === 422) {
          this.errors = error.response.data.errors;
          this.errorMessage = 'Please fix the errors above and try again.';
        } else {
          this.errorMessage = error.response ? error.response.data.error : 'An error occurred.';
          console.error(error);
        }
      } finally {
        this.loading = false;
      }
    },
  },
};
</script>

<style scoped>
/* Add any custom styles here */
</style>

To use a header for authorization in Vue.js with the axios library, you'll need to include the authorization token in the request headers when making an API call. Here's how you can modify your categoryInsert method to include an authorization header:


methods: {
    async categoryInsert() {
        this.loading = true;
        const token = localStorage.getItem('token'); // Retrieve the token from localStorage
        try {
            const response = await axios.post('/api/category', this.form, {
                headers: {
                    'Authorization': `Bearer ${token}`, // Add the Authorization header
                    'Content-Type': 'application/json'
                }
            });
            if (response.data.success) { // Check if the response is successful
                this.$router.push({ name: "ViewCategory" });
                Notification.success(response.data.message); // Display success message
            } else {
                Notification.error(response.data.message);
            }
        } catch (error) {
            if (error.response) {
                if (error.response.status === 400) {
                    Notification.error(error.response.data.message);
                }
                this.errors = error.response.data.errors || {};
            } else {
                console.error('An error occurred:', error.message);
            }
        } finally {
            this.loading = false;
        }
    },
},

Explanation:
    Authorization Header: The Authorization header is added to the request using the headers property in the axios.post method. The token is retrieved from localStorage and included as Bearer ${token}.

    Content-Type Header: The Content-Type header is set to 'application/json' to indicate that the request body is in JSON format.

    Error Handling: The method still includes error handling to manage any issues that arise during the API request.

By following these steps, you've successfully set up JWT authentication in your Laravel and Vue.js application. This includes configuring Axios for API requests, securing routes with middleware, creating an authentication controller, and building a login component. This setup ensures secure and efficient user authentication, enhancing the overall functionality of your application.


0 Comments
Leave a Comment

Video