Токены Laravel JWT становятся недействительными после обновления их в подходе JWT аутентификации

РЕДАКТИРОВАТЬ:

Прочитайте обсуждение об ошибке по адресу: https://github.com/tymondesigns/jwt-auth/issues/83

МОЙ ОРИГИНАЛЬНЫЙ ВОПРОС:

Я реализую с JWT-авт мои защищенные ресурсы, которые требуют аутентифицированного пользователя с приведенным ниже кодом:

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

Когда пользователь «входит» в API, создается токен авторизации и отправляется в ответ на запрос заголовка авторизации клиентскому приложению, которое вызывает ресурс. Таким образом, клиентские приложения при перехвате токена авторизации в заголовке любого ответа устанавливают переменную / сессию / что угодно с этим значением токена, чтобы снова отправлять в API при следующем запросе.

Первый запрос защищенного ресурса после ‘login’ работает нормально, но следующий запрос клиентского приложения к API с обновленным токеном выдает следующую ошибку (API монтирует все ответы в формате json):

{
"error": "token_invalid"}

Что может случиться с обновленными токенами? Моя реализация токена обновления (установленная как промежуточное ПО) неверна? Или нет необходимости вручную обновлять все токены авторизации, которые идут с запросами клиентских приложений?

ОБНОВИТЬ:

Я обновляю промежуточное ПО jwt-auth RefreshToken в соответствии с предложением Вот, но token_invalid сохраняются.

BUG:

Я думаю, что я нашел, что происходит. Обратите внимание, что в методе обновления старый токен добавляется в кэш черного списка:

// 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']
])
);
}

И обратите внимание, что в методе add to blacklist ключом является параметр 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;
}

Таким образом, когда вызывается метод в черном списке, старый параметр токена jti совпадает с новым, поэтому новый токен находится в черном списке:

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

Если вам не нужна функциональность черного списка, просто установите false в конфигурационном файле jwt.php. Но я не могу сказать, если это подвергается какой-либо уязвимости безопасности.

Прочитайте обсуждение об ошибке по адресу: https://github.com/tymondesigns/jwt-auth/issues/83

9

Решение

Когда я получил эту проблему, решение, которое я нашел, чтобы заставить мой проект работать, состояло в том, чтобы генерировать новый токен с данными из старого токена при каждом новом запросе.

Мое решение, которое работает для меня, плохо, уродливо и может вызвать больше проблем, если у вас много асинхронных запросов и ваш сервер API (или бизнес-ядро) работает медленно.

Пока работает, но я буду больше исследовать эту проблему, потому что после версии 0.5.3 проблема продолжается.

Например:

Запрос 1 (GET / логин):

Some guest data on token

Запрос 2 (POST / вход в систему):

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

Пример процедурного кода (вы можете сделать лучше =)), вы можете запустить это на rout.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

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]