Reputation: 92347
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.
php artisan route:list
) by remove 'middleware' => 'web'
(If I don't do it, i will see CSRF problem with post request).Route::group(['middleware' =>'api', 'prefix' => '/api/v1', 'namespace' => 'Api\V1'], function () { Route::post('/login', 'Auth\AuthController@postLogin'); ... Route::get('/projects', 'ProjectsController@getProjects'); }
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);
And, yeah here question starts. I would like to do something (may be modifiy laravel Auth
class) on each request:
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.
Upvotes: 7
Views: 13897
Reputation: 1551
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.
Upvotes: -1
Reputation: 92347
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();
}
Upvotes: 2
Reputation: 1227
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);
Upvotes: 3