如何在 Laravel 中进行 JWT cookie 身份验证 [英] How to make JWT cookie authentication in Laravel

查看:60
本文介绍了如何在 Laravel 中进行 JWT cookie 身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在 Laravel >=5.2 中使用 JWT 身份验证,使用 这个(Tymon JWT-auth)库 但我想将 JWT 令牌放入 HttpOnly Cookies - 以保护 JWT 令牌免受 XSS 攻击.

  1. 我设置了 Tymon 库并...在项目中:app/Providers/RouteServiceProvider@mapWebRoutes 我停用了所有请求的执行 'web' 中间件组(这是默认的 Laravel 行为 - 您可以通过 php artisan 看到它route:list) 通过删除 'middleware' =>'web'(如果我不这样做,我会在 post 请求中看到 CSRF 问题).
  2. 在routes.php中我写:

<块引用>

Route::group(['middleware' =>'api', 'prefix' => '/api/v1', 'namespace' => 'Api\V1'], function (){Route::post('/login', 'Auth\AuthController@postLogin');...Route::get('/projects', 'ProjectsController@getProjects');}

  1. 在 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);

  2. 而且,是的,问题开始了.我想对每个请求做一些事情(可能是修改 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.

  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.

解决方案

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆