Laravel JWT Multi-Page Structure Laravel JWT Multi-Page Structure vue.js vue.js

Laravel JWT Multi-Page Structure


On successful login, you will have the token, let's say its called $jwt_token

You can redirect to the page you are protecting once authorized and set the cookie in the response:

return redirect('/home')->cookie(    'access_token', //name    $jwt_token,  //value    config('session.lifetime'), //expiration in minutes (matches laravel)    config('app.url'),  // your app url    true // HttpsOnly);

From here, Axios can have access to the cookie by parsing the cookies on the document and retrieving the access_token

let token = document.cookie.split(';') // get all your cookies    .find(cookie => cookie.includes('access_token')) // take only the one that matches our access_token name    .split('=')[1] // get just the value after =// terrible code example above for you

Now you can use this in your Axios requests by adding it as the value to Bearer in the Authorization header:

Authorization: `Bearer ${token}`

Your JWT middleware already leverage the authenticate method and therefore it should be handling the expiry for you as it stands:

JWTAuth::parseToken()->authenticate();

Under the hood this will attempt to validate the token's expiry based on the current TTL set in the config/jwt.php file. Given your work flow, I would also blacklist the token if it expires. You can add an Event Listener that listens for expired tokens and blacklists them by listening to Event::listen('tymon.jwt.expired');.

Please excuse any syntax errors, formatting issues or misspellings, I'm on my pone and will edit later to resolve those.


So there are a couple of ways you can make sure the JWT token is available everywhere for Axios or indeed any frontend to use.

The most common way is to store the token in either a cookie or in the Web Storage of the browser (localStorage / sessionStorage)

The difference between localStorage and sessionStorage is that data stored in localStorage persists through browser sessions, sessionStorage is cleared when the page session ends.

The general consensus is that cookies are slightly more secure because they have a smaller attack vector, though neither method is completely secure. If you want to go more in depth you can start by reading this article.

To get more specific concerning your problem, first you want to setup token storage using one of the methods explained above, recommended method is cookies, you can find examples of how to do it with pure Javascript here.

Now that you have the token on every page you can redirect the user whichever way you like. Though I would suggest that instead of using your own middleware for JWT authentication, you can use the one that the JWT library provides: jwt.auth.

This middleware will automatically respond with error codes if something is wrong with the token, if there is it will return one of the following HTTP responses:

  • token_not_provided
  • token_expired
  • token_invalid
  • user_not_found

If one of these responses are returned (or if the request status code is 400) you can simply use the frontend to redirect the user back to your pre-auth routes.

When signing in, after saving the token to a cookie, use the frontend to redirect to the post-auth routes.

I know you said you wanted to keep redirect logic in the backend but that doesn't really make sense when you're for example calling the API when you're signing in, you can't really both return the token and cause a redirect at the same time from just the backend.

UPDATE

Very simple example of how you can authenticate with only guard and still get a token for the API. Borrowing from the redirect example from @Ohgodwhy, you can put the following inside your RedirectIfAuthenticated middleware.

public function handle($request, Closure $next, $guard = null)    if (Auth::guard($guard)->check()) {        if ((\Cookie::get('access_token') == null)) {            $cookie = \Cookie::make(                'access_token',                \JWTAuth::fromUser(Auth::user()),                config('session.lifetime'),                null,                $request->refeerer,                false, // to make the cookie available in javascript                false // to make the cookie available in javascript            );            return redirect('/home')->cookie($cookie);        } else {            return redirect('/home');        }    }    return $next($request);}

Just make sure that your $redirectTo in app/Http/Controllers/Auth/LoginController.php is set to a path that implements the RedirectIfAuthenticated middleware.


So here's the logic I ended up implementing:

In my LoginController.php login function, after successful authentication and generation of JWT, I return a response with Json and a new cookie, both with new token passed:

public function login(Request $request){    $creds = $request->only(['email', 'password']);    if (!$token = auth()->attempt($creds)) {        return response()->json([            'errors' => [                'root' => 'Incorrect Credentials. Try again'            ]        ], 401);    }    return $this->respondWithToken($token);}protected function respondWithToken($token){    return response()->json([        'meta' => [            'access_token' => $token,            'token_type' => 'bearer',            'expires_in' => auth()->factory()->getTTL() * 60        ]    ], 200)    ->withCookie(cookie('access_token', $token, auth()->factory()->getTTL()));}

In my guest RedirectIfAuthenticated middleware check for cookie, if exists, setToken which in turn sets Guard to Authenticated and will always redirect to /home if token is available and valid:

public function handle($request, Closure $next, $guard = null){    if ($request->hasCookie('access_token')) {        Auth::setToken($request->cookie('access_token'));    }    if (Auth::guard($guard)->check()) {        return redirect('/home');    }    return $next($request);}

And In my post-auth Routes middleware I also setToken and if its valid and exists, will allow access, otherwise will throw a range of JWT errors which just redirect to pre-auth view:

public function handle($request, Closure $next){    JWTAuth::setToken($request->cookie('access_token'))->authenticate();    return $next($request);}

Finally, I decided to handle redirection in the front-end being I'm using Axios which is promised based and can assure that cookie will be set before redirecting to post-auth view so no funny business happens! Cheers! Hope this helps anyone on their quest to Multi-Page SPA magic!