7

I want to have JWT authentication in Laravel >=5.2, using this (Tymon JWT-auth) library but I want to put JWT token into HttpOnly Cookies - to protect JWT token from steal from XSS attack.

  1. I set up Tymon library and... in project: app/Providers/RouteServiceProvider@mapWebRoutes i deactivate execution 'web' middelware group for all requests (which is default laravel behavior - you can see it by php artisan route:list) by remove 'middleware' => 'web' (If I don't do it, i will see CSRF problem with post request).
  2. in routes.php i write:
Route::group(['middleware' =>'api', 'prefix' => '/api/v1', 'namespace' => 'Api\V1'], function () {
    Route::post('/login', 'Auth\AuthController@postLogin');
    ...
    Route::get('/projects', 'ProjectsController@getProjects');
}
  1. In may Api\V1\Auth\AuthController@postLogin i generate token and send it back as httpOnly cookie:

    ...
    try
    {
        $user = User::where('email','=',$credentials['email'])->first();
    
        if ( !($user && Hash::check($credentials['password'], $user->password) ))
        {
            return response()->json(['error' => 'invalid_credentials'], 401);
        }
    
        $customClaims = ['sub' => $user->id, 'role'=> $user->role, 'csrf-token' => str_random(32) ];
        $payload = JWTFactory::make($customClaims);
        $token = JWTAuth::encode($payload);
    } catch(...) {...}
    return response()->json($payload->toArray())->withCookie('token', $token, config('jwt.ttl'), "/", null, false, true); 
    
  2. And, yeah here question starts. I would like to do something (may be modifiy laravel Auth class) on each request:

    • get coookie from request
    • decode it
    • check is right (if not trhow 401)
    • get user from DB
    • and make that method Auth::user() works every where like in usual way in laravel (so i can use it in each Controller for example)

Any ideas how to do point 4 ?

UPDATE

I also add here protection for CSRF attack - csrf-token is in JWT, and it is also return in body of response for login request (so JS have acces to this csrf-token) (i return only public part of JWT token in login response, whole JWT is return only in cookie, so it is XSS safe) - then front JS must copy csrf-token into header of each request. Then the middelware JWTAuthentiacate (in my answer below) compare csrf-token header with csrf-token field in JWT payload - if they are similar then request pass csrf test.

1
  • @Atlas-Pio it was long time ago and I no't remember - for 2 years I less use Laravel and more focus on frontend (Angular) - but I think - all informations are in this question - and in my answer below - however If you have some concrete problem to set it up - create new stack overflow question and give me link here in comment - may be I can help Commented Sep 22, 2020 at 11:10

3 Answers 3

3

You can do it simple by creating middleware.

In handle() method just get cookie from request, decode it and login a user using id with this Laravel method:

Auth::loginUsingId($userIdFromToken);
Sign up to request clarification or add additional context in comments.

1 Comment

Wow - yeach - it's briliant idea! I implement It and works exactly what I want :)
2

I implement @ŁukaszKuczmaja idea in this way an it works! :) . So i create file in app/Http/Middleware/JWTAuthenticate.php :

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use JWTAuth;
use Tymon\JWTAuth\Token;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Illuminate\Session\TokenMismatchException;

class JWTAuthenticate
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        try {
            if(!$request->headers->has('csrf-token')) throw new TokenMismatchException();
            $rawToken = $request->cookie('token');
            $token = new Token($rawToken);
            $payload = JWTAuth::decode($token);
            if($payload['csrf-token'] != $request->headers->get('csrf-token')) throw new TokenMismatchException();
            Auth::loginUsingId($payload['sub']);
        } catch(\Exception $e) {
            if( $e instanceof TokenExpiredException) {
                // TODO token refresh here
            }
            return response('Unauthorized.', 401);
        }

        return $next($request);
    }
}

In app\Http\Kernel.php@$routeMiddelware I add line:

'jwt.auth'    => \App\Http\Middleware\JWTAuthenticate::class,

My routing file looks like this now:

Route::group(['middleware' =>'api', 'prefix' => '/api/v1', 'namespace' => 'Api\V1'], function () {

    Route::post('/login', 'Auth\AuthController@postLogin');

    Route::group(['middleware' =>'jwt.auth'], function () {
        Route::post('/projects', 'ProjectsController@postProjects');
        Route::get('/projects', 'ProjectsController@getProjects');
        Route::put('/projects/{project}', 'ProjectsController@putProjects');
        Route::delete('/projects/{project}', 'ProjectsController@deleteProjects');
    });

});

And for instance in app/Http/Controllers/Api/V1/ProjectsController.php i have:

public function getProjects() {
    $uid = Auth::user()->id;
    return Project::where('user_id','=',$uid)->get();
}

2 Comments

Kamil, hi! I use jwt-auth in approach with httponly cookie, but I'm stuck with csrf-token and api auth. How should I get the token from the server and where should I store it? Should I get it with the first request to the API? And is it good idea to store it in the localstorage? If I will send it with the cookie and store it in the browser storage, it will be send back with every request to the server and it makes no sense
Look on my QUESTION update - details are there. Alternatively you can also turn of crsf token for API like e.g. here
-1

Actually you can put every route that needs authentication within a route group and add the middleware like this:

Route::group(['middleware' => ['jwt.auth']], function () {
    Route::patch('/profile', 'UserController@update');
});

The middleware already does what you wanted so there is no need to write additional logic. Don't use an additional handle method.

Within your i.e. UserController you can then i.e.

$user = \Auth::user();

And i.e. depending what you need...

// assign fields
$user->save();

return 'success'; // or whatever you need

Don't reinvent the wheel and keep things DRY.

1 Comment

I guess you didn't understand the question. It's not about using jwt as it comes out of box, it's about using jwt as httpOnly cookie, not with Bearer auth. You can't use jwt as httpOnly cookie without any custom middleware.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.