Как стать автором
Обновить

Комментарии 11

Что же происходит когда token expires?

Здравствуйте, вот простая проверка на время жизни, если оно истекло, вызываю HTTPException:

if datetime.fromtimestamp(float(exp)) - datetime.now() < timedelta(0):
                raise credentials_exception
credentials_exception = HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

Возможна и другая логика при истечении времени токена

Я про клиент спрашивал.

А чего код не оформили? :) Где-то отступы есть, где-то нет. Где-то пробелы в параметрах метода есть, где-то нет и т.п. Где-то типы возвращаемых значений поставили, а где-то нет.

Даже для пет-проекта решение как минимум неудобно:

  1. Настройки токена зашиты жестко в код. При этом есть какой-то объект настроек (видимо, что-то на базе pydantic-settings). Почему бы не использовать его для всех настроек?

  2. Поведение функции validate_user максимально неудобно. Зачем возвращать из нее булево значение, если можно выкинуть исключение? При этом вы дальше как раз на базе этого значения кидаете исключение. Ну и else в этой функции лишний. Почитайте про guard clause.

  3. Логирования нет.

  4. Работа с исключениями в get_current_user заставила нервно улыбнуться. Не надо так писать :) Да и вообще вся функция просится на переработку

  5. В строке запуска ювикорна какая-то лажа

В js я не волоку, но жонглирование типами, например в переменной ans в getUserByToken -- явно не самая клевая идея.

Резюме для всех, кто захочет применить это на практике. Не надо. Возьмите какой-нит fastapi-users и не парьтесь.

Приветствую вас, спасибо за обратную связь, она очень полезна на начале моего пути, знаю, что в коде есть много элементов, которые можно было бы написать иначе(ну а в каком коде их нет), обязательно в дальнейшем учту ваши замечания! Если будет что то еще, обязательно постараюсь учесть.

А чего пароль в токен не добавили? Что-то вроде «доя упрощения примера я засунул юзера вместе с паролем в токен, но вы можете так не делать»

Как реализовать функцию "выйти со всех устройств"?

Подход с чистым jwt токеном не очень безопасен, так как в случае компрометации учётной записи (это гораздо проще, чем кажется, достаточно войти через публичный компьютер и забыть выйти), пользователь ничего с этим не может сделать - полученный злоумышленником токен будет действовать как обычный.

Как я понимаю, чистый jwt имеет смысл либо совсем короткоживущий (секунды, как промежуточный ключ авторизации в каком-то более сложном процессе), либо при общении между микросервисами (когда токен полученный от пользователя уже валидирован фронтовым микросервисом каким-то stateful способом). Во всех остальных случаях он является огромной дырой.

Закрыть эту дыру можно механизмом отзыва токенов. Хранить в каком-нибудь Redis список отозванных токенов. Такая проверка будет быстрее, чем по БД.

В свою очередь в своих петах я реализую лениво просто один токен на все сессии. У юзера в БД есть колонка auth_token. Если там NULL в момент авторизации, генерирую случайную строку, кладу в БД и отдаю юзеру. Если там уже что-то есть, возвращаю то что есть. Все запросы начинаются с поиска в БД юзера по токену (это, конечно же, колонка с уникальным индексом). Выход с одного устройства осуществляется просто забыванием токена без запроса на сервер. Выход со всех устройств приводит к записи NULL в колонку токена (или можно сразу перегенерировать токен, если нам нужна функция "выйти со всех устройств, кроме текущего").

Соответственно, таким образом как и с JWT мы не плодим строчки на каждую забытую сессию и не нуждаемся в JOIN с информацией о пользователе, зато позволяем юзеру прибить все сессии в случае компрометации учётной записи.

Можно дополнительно завернуть такой токен в JWT, если хочется индивидуального времени жизни токенов. Хотя я такое не люблю, я предпочитаю сайты с вечной авторизацией (собственно, все популярные сервисы типа гугла или вконтакте авторизуют пользователя навсегда). Если хочется особой секурности, можно сделать два токена. Один вечный, другой сессионный. Все запросы идут со вторым, но если приходит ошибка 401, то через специальный эндпойнт и вечный токен, получается новый сессионный токен (таким образом мы значительно понижаем шансы при MITM, что утечет вечный токен, ибо его ещё надо поймать). Но для пета скорее всего это будет излишне, так как кража токена прямо из HTTPS маловероятна в целом.

Мне видится основными сценариями атаки либо вирус на компьютере (в таком случае следует полагать, что украдено всё - и все токены, и пароли, единственное, что может сделать пользователь - сменить пароль и воспользоваться функцией "выйти со всех устройств"), либо забыл выйти с общего компьютера (поможет только "выйти со всех устройств"). Ещё есть фишинг, но там как и в случае с вирусом без смены пароля и "выйти со всех устройств" не обойтись.

Спасибо за ваш за развернутый и действительно полезный отзыв! Я обязательно попробую реализовать такой сценарий использования. У чистого JWT,конечно,есть свои недостатки и в таком виде использовать его в продакшне небезопасно,нужно делать более сложную структуру

Также я бы добавил, что секретный ключ JWT критически важно надёжно хранить и уж точно не копипастить из статьи (знание ключа позволяет генерировать jwt с ЛЮБЫМ содержанием, которые будут неотличимы от легитимных). Если очень лень реализовывать механизм конфигурации и генерировать свой ключ, можно генерировать случайный ключ при запуске приложения. Тогда, конечно, перезапуск приложения будет грохать все активные сессии, но это лучше, чем использовать чужой ключ.

На месте автора статьи, я бы вставил сниппет с генерацией при запуске.

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

Хранить в JWT токене пароль - не безопасно и абсолютно не нужно, достаточно проверять один раз при создании токена. Даже для примера так делать не нужно.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории