如何在 Laravel 中进行 JWT cookie 身份验证 [英] How to make JWT cookie authentication in Laravel
问题描述
我想在 Laravel >=5.2 中使用 JWT 身份验证,使用 这个(Tymon JWT-auth)库 但我想将 JWT 令牌放入 HttpOnly Cookies - 以保护 JWT 令牌免受 XSS 攻击.
- 我设置了 Tymon 库并...在项目中:app/Providers/RouteServiceProvider@mapWebRoutes 我停用了所有请求的执行 'web' 中间件组(这是默认的 Laravel 行为 - 您可以通过
php artisan 看到它route:list
) 通过删除'middleware' =>'web'
(如果我不这样做,我会在 post 请求中看到 CSRF 问题). - 在routes.php中我写:
<块引用>
Route::group(['middleware' =>'api', 'prefix' => '/api/v1', 'namespace' => 'Api\V1'], function (){Route::post('/login', 'Auth\AuthController@postLogin');...Route::get('/projects', 'ProjectsController@getProjects');}
在 Api\V1\Auth\AuthController@postLogin 中,我生成令牌并将其作为 httpOnly cookie 发回:
<代码>...尝试{$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);} 抓住(...) {...}return response()->json($payload->toArray())->withCookie('token', $token, config('jwt.ttl'), "/", null, false, true);
而且,是的,问题开始了.我想对每个请求做一些事情(可能是修改 laravel
Auth
类):- 从请求中获取 cookie
- 解码
- 检查是正确的(如果不是 trhow 401)
- 从数据库获取用户
- 并使该方法 Auth::user() 像在 laravel 中的通常方式一样在任何地方工作(例如,我可以在每个控制器中使用它)
知道如何做第 4 点吗?
更新
我还在此处添加了对 CSRF 攻击的保护 - csrf-token 在 JWT 中,它也在登录请求的响应正文中返回(因此 JS 可以访问此 csrf-token)(我只返回 JWT 的公共部分登录响应中的令牌,整个 JWT 仅在 cookie 中返回,因此它是 XSS 安全的) - 然后前端 JS 必须将 csrf-token 复制到每个请求的标头中.然后中间件 JWTAuthentiacate(在我下面的回答中)将 csrf-token 标头与 JWT 有效负载中的 csrf-token 字段进行比较 - 如果它们相似,则请求通过 csrf 测试.
我以这种方式实现了@ŁukaszKuczmaja 的想法并且它有效!:) .所以我在 app/Http/Middleware/JWTAuthenticate.php
中创建文件:
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 令牌在这里刷新}return response('未经授权', 401);}返回 $next($request);}}
在 app\Http\Kernel.php@$routeMiddelware
中添加一行:
'jwt.auth' =>\App\Http\Middleware\JWTAuthenticate::class,
我的路由文件现在看起来像这样:
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');});});
例如在 app/Http/Controllers/Api/V1/ProjectsController.php
我有:
公共函数 getProjects() {$uid = Auth::user()->id;return Project::where('user_id','=',$uid)->get();}
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.
- 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). - 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'); }
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:- 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.
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();
}
这篇关于如何在 Laravel 中进行 JWT cookie 身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!