web-dev-qa-db-ja.com

Laravel JWTトークンは、認証JWTアプローチで更新した後は無効です

編集:

バグに関するディスカッションを次の場所で読んでください: https://github.com/tymondesigns/jwt-auth/issues/8

私の最初の質問:

私は jwt-auth で実装しています。保護されたリソースには、次のコードで認証されたユーザーが必要です。

Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function() {
    // Protected routes
});

ユーザーがAPIで「サインイン」すると、承認トークンが作成され、応答の承認ヘッダーで、リソースを呼び出すクライアントアプリケーションに送信されます。したがって、クライアントアプリケーションは、応答のヘッダーでAuthorizationトークンをインターセプトするときに、このトークン値を使用して変数/セッション/その他を設定し、次のリクエストでAPIに再度送信します。

'login'後の保護されたリソースに対する最初のリクエストは正常に機能しますが、更新されたトークンを使用したAPIへの次のクライアントアプリケーションリクエストでは、次のエラーが発生します(APIはすべての応答をjson形式でマウントします)。

{
    "error": "token_invalid"
}

更新されたトークンで何が起こる可能性がありますか?私のリフレッシュトークンの実装(アフターミドルウェアとして設定)は間違っていますか?または、クライアントアプリのリクエストに付属するすべての認証トークンを手動で更新する必要はありませんか?

更新:

Jwt-auth RefreshTokenミドルウェアを提案として更新します ここ 、しかしtoken_invalid持続します。

バグ:

私は何が起こるかを見つけたと思います。 refreshメソッドでは、有効なブラックリストキャッシュケースに古いトークンが追加されることに注意してください。

// Tymon\JWTAuth\JWTManager
public function refresh(Token $token)
{
    $payload = $this->decode($token);

    if ($this->blacklistEnabled) {
        // invalidate old token
        $this->blacklist->add($payload);
    }

    // return the new token
    return $this->encode(
        $this->payloadFactory->setRefreshFlow()->make([
            'sub' => $payload['sub'],
            'iat' => $payload['iat']
        ])
    );
}

また、ブラックリストメソッドに追加する場合、キーは古いトークンペイロードのjtiパラメータであることに注意してください。

// Tymon\JWTAuth\Blacklist
public function add(Payload $payload)
{
    $exp = Utils::timestamp($payload['exp']);

    // there is no need to add the token to the blacklist
    // if the token has already expired
    if ($exp->isPast()) {
        return false;
    }

    // add a minute to abate potential overlap
    $minutes = $exp->diffInMinutes(Utils::now()->subMinute());

    $this->storage->add($payload['jti'], [], $minutes);

    return true;
}

したがって、has on blacklistメソッドが呼び出されると、古いトークンのjti paramは新しいものと同じになるため、新しいトークンはブラックリストに含まれます。

// Tymon\JWTAuth\Blacklist
public function has(Payload $payload)
{
    return $this->storage->has($payload['jti']);
}

ブラックリスト機能が必要ない場合は、jwt.php構成ファイルでfalseに設定するだけです。しかし、それが何らかのセキュリティの脆弱性にさらされているかどうかはわかりません。

バグに関するディスカッションを次の場所で読んでください: https://github.com/tymondesigns/jwt-auth/issues/8

9
Maykonn

この問題が発生したとき、プロジェクトを機能させるために見つけた解決策は、新しいリクエストごとに古いトークンのデータを使用して新しいトークンを生成することでした。

私の解決策は、私にとってはうまく機能しますが、ひどく醜く、非同期リクエストが多く、API(またはビジネスコア)サーバーが遅い場合、より多くの問題が発生する可能性があります。

今のところは機能していますが、この問題についてさらに調査します。0.5.3バージョン以降も問題が続くためです。

例えば:

リクエスト1(GET/login):

Some guest data on token

リクエスト2(POST /ログイン応答):

User data merged with guest data on old token generating a new token

手続き型コードの例(あなたはもっとうまくやることができます=))、あなたはこれをroutes.phpでルートから実行することができます、私はそれが醜いハハだと言います:

// ----------------------------------------------------------------
// AUTH TOKEN WORK
// ----------------------------------------------------------------
$authToken = null;
$getAuthToken = function() use ($authToken, $Response) {
    if($authToken === null) {
         $authToken = JWTAuth::parseToken();
    }
    return $authToken;
};

$getLoggedUser = function() use ($getAuthToken) {
    return $getAuthToken()->authenticate();
};

$getAuthPayload = function() use ($getAuthToken) {
    try {
        return $getAuthToken()->getPayload();
    } catch (Exception $e) {
        return [];
    }
};

$mountAuthPayload = function($customPayload) use ($getLoggedUser, $getAuthPayload) {
    $currentPayload = [];
    try {
        $currentAuthPayload = $getAuthPayload();
        if(count($currentAuthPayload)) {
            $currentPayload = $currentAuthPayload->toArray();
        }
        try {
            if($user = $getLoggedUser()) {
                $currentPayload['user'] = $user;
            }
            $currentPayload['isGuest'] = false;
        } catch (Exception $e) {
            // is guest
        }
    } catch(Exception $e) {
        // Impossible to parse token
    }

    foreach ($customPayload as $key => $value) {
        $currentPayload[$key] = $value;
    }

    return $currentPayload;
};

// ----------------------------------------------------------------
// AUTH TOKEN PAYLOAD
// ----------------------------------------------------------------
try {
    $getLoggedUser();
    $payload = ['isGuest' => false];
} catch (Exception $e) {
    $payload = ['isGuest' => true];
}

try {
    $payload = $mountAuthPayload($payload);
} catch (Exception $e) {
    // Make nothing cause token is invalid, expired, etc., or not exists.
    // Like a guest session. Create a token without user data.
}

いくつかのルート(ユーザーのモバイルデバイスを保存する簡単な例):

Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function () use ($getLoggedUser, $mountAuthPayload) {
    Route::post('/session/device', function () use ($Response, $getLoggedUser, $mountAuthPayload) {
        $Response = new \Illuminate\Http\Response();
        $user = $getLoggedUser();

        // code to save on database the user device from current "session"...

        $payload = app('tymon.jwt.payload.factory')->make($mountAuthPayload(['device' => $user->device->last()->toArray()]));
        $token = JWTAuth::encode($payload);
        $Response->header('Authorization', 'Bearer ' . $token);

        $responseContent = ['setted' => 'true'];

        $Response->setContent($responseContent);
        return $Response;
    });
});
4
Maykonn