Комментарии 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"},
)
Возможна и другая логика при истечении времени токена
А чего код не оформили? :) Где-то отступы есть, где-то нет. Где-то пробелы в параметрах метода есть, где-то нет и т.п. Где-то типы возвращаемых значений поставили, а где-то нет.
Даже для пет-проекта решение как минимум неудобно:
Настройки токена зашиты жестко в код. При этом есть какой-то объект настроек (видимо, что-то на базе pydantic-settings). Почему бы не использовать его для всех настроек?
Поведение функции validate_user максимально неудобно. Зачем возвращать из нее булево значение, если можно выкинуть исключение? При этом вы дальше как раз на базе этого значения кидаете исключение. Ну и
else
в этой функции лишний. Почитайте про guard clause.Логирования нет.
Работа с исключениями в
get_current_user
заставила нервно улыбнуться. Не надо так писать :) Да и вообще вся функция просится на переработкуВ строке запуска ювикорна какая-то лажа
В js я не волоку, но жонглирование типами, например в переменной ans в getUserByToken -- явно не самая клевая идея.
Резюме для всех, кто захочет применить это на практике. Не надо. Возьмите какой-нит fastapi-users и не парьтесь.
А чего пароль в токен не добавили? Что-то вроде «доя упрощения примера я засунул юзера вместе с паролем в токен, но вы можете так не делать»
Как реализовать функцию "выйти со всех устройств"?
Подход с чистым jwt токеном не очень безопасен, так как в случае компрометации учётной записи (это гораздо проще, чем кажется, достаточно войти через публичный компьютер и забыть выйти), пользователь ничего с этим не может сделать - полученный злоумышленником токен будет действовать как обычный.
Как я понимаю, чистый jwt имеет смысл либо совсем короткоживущий (секунды, как промежуточный ключ авторизации в каком-то более сложном процессе), либо при общении между микросервисами (когда токен полученный от пользователя уже валидирован фронтовым микросервисом каким-то stateful способом). Во всех остальных случаях он является огромной дырой.
Закрыть эту дыру можно механизмом отзыва токенов. Хранить в каком-нибудь Redis список отозванных токенов. Такая проверка будет быстрее, чем по БД.
В свою очередь в своих петах я реализую лениво просто один токен на все сессии. У юзера в БД есть колонка auth_token. Если там NULL в момент авторизации, генерирую случайную строку, кладу в БД и отдаю юзеру. Если там уже что-то есть, возвращаю то что есть. Все запросы начинаются с поиска в БД юзера по токену (это, конечно же, колонка с уникальным индексом). Выход с одного устройства осуществляется просто забыванием токена без запроса на сервер. Выход со всех устройств приводит к записи NULL в колонку токена (или можно сразу перегенерировать токен, если нам нужна функция "выйти со всех устройств, кроме текущего").
Соответственно, таким образом как и с JWT мы не плодим строчки на каждую забытую сессию и не нуждаемся в JOIN с информацией о пользователе, зато позволяем юзеру прибить все сессии в случае компрометации учётной записи.
Можно дополнительно завернуть такой токен в JWT, если хочется индивидуального времени жизни токенов. Хотя я такое не люблю, я предпочитаю сайты с вечной авторизацией (собственно, все популярные сервисы типа гугла или вконтакте авторизуют пользователя навсегда). Если хочется особой секурности, можно сделать два токена. Один вечный, другой сессионный. Все запросы идут со вторым, но если приходит ошибка 401, то через специальный эндпойнт и вечный токен, получается новый сессионный токен (таким образом мы значительно понижаем шансы при MITM, что утечет вечный токен, ибо его ещё надо поймать). Но для пета скорее всего это будет излишне, так как кража токена прямо из HTTPS маловероятна в целом.
Мне видится основными сценариями атаки либо вирус на компьютере (в таком случае следует полагать, что украдено всё - и все токены, и пароли, единственное, что может сделать пользователь - сменить пароль и воспользоваться функцией "выйти со всех устройств"), либо забыл выйти с общего компьютера (поможет только "выйти со всех устройств"). Ещё есть фишинг, но там как и в случае с вирусом без смены пароля и "выйти со всех устройств" не обойтись.
Также я бы добавил, что секретный ключ JWT критически важно надёжно хранить и уж точно не копипастить из статьи (знание ключа позволяет генерировать jwt с ЛЮБЫМ содержанием, которые будут неотличимы от легитимных). Если очень лень реализовывать механизм конфигурации и генерировать свой ключ, можно генерировать случайный ключ при запуске приложения. Тогда, конечно, перезапуск приложения будет грохать все активные сессии, но это лучше, чем использовать чужой ключ.
На месте автора статьи, я бы вставил сниппет с генерацией при запуске.
Хранить в JWT токене пароль - не безопасно и абсолютно не нужно, достаточно проверять один раз при создании токена. Даже для примера так делать не нужно.
Мой вариант аутентификации с помощью JWT в FastAPI + React