Django/Django Rest Как сохранить устройство пользователя для предотвращения утомительного 2FA при каждом входе?
Здравствуйте, я работаю с Django Rest Framework с JWT в качестве фреймворка аутентификации и я успешно сделал двухфакторную аутентификацию Login на основе Email OTP, но одну вещь я хочу улучшить, я хочу улучшить логин и сохранить устройство пользователя, чтобы повторная 2FA (двухфакторная аутентификация) могла быть минимизирована?
вот определенный пример кода, который я сделал для отправки otp на email пользователя.
serializers.py
class UserLoginSerializer(serializers.Serializer):
email = serializers.EmailField()
password = PasswordField()
views.py
classUserLoginView(generics.CreateWithMessageAPIView):
"""
Use this end-point to get login for user
"""
message = _('Please check your email for 6 digit OTP code.')
serializer_class = serializers.UserLoginSerializer
def perform_create(self, serializer):
usecases.UserLoginWithOTPUseCase(self.request, serializer=serializer).execute()
usecases.py
class UserLoginWithOTPUseCase(CreateUseCase, OTPMixin):
def __init__(self, request, serializer):
self._request = request
super().__init__(serializer)
def execute(self):
self._factory()
def _factory(self):
credentials = {
'username': self._data['email'],
'password': self._data['password']
}
self._user = authenticate(self._request, **credentials)
if self._user is not None:
"""
Sends email confirmation mail to the user's email
:return: None
"""
code = self._generate_totp(
user=self._user,
purpose='2FA',
interval=180
)
EmailVerificationEmail(
context={
'code': code,
'uuid': self._user.id
}
).send(to=[self._user.email])
else:
raise PermissionDenied(
{
'authentication_error': _('User name or password not matched')
}
)
Я запутался, как я могу разрешить или сохранить устройство для предотвращения повторного 2FA.
TLDR;
На очень высоком уровне: токенизация ОТП (обмен otp
на JWT
).
Пояснение
А JWT
- это не более чем JSON
подписанная полезная нагрузка с некоторыми стандартизированными полями exp
(срок действия), nbf
(не ранее), и т.д. . Подпись (симметричная или асимметричная) гарантирует целостность, подлинность и неотказуемость (eg токен был выпущен тем, кем мы думаем, и не был изменен).
В полезную нагрузку JWT
можно поместить что угодно (помня, что значение не шифруется), включая данные, необходимые для логики вашего приложения. Вы также можете обмениваться различными JWT
во время различных этапов аутентификации.
Некоторые примеры
Отложенная 2FA
- Пользователь дает вам
<username, password>
если оба действительны, он получаетtype_1_jwt
, если 2FA включена, илиtype_2_jwt
.
- Пользователь дает вам
<type_1_jwt, otp>
если оба действительны, он получаетtype_2_jwt
.
- Ваше приложение принимает только
type_2_jwt
на всех остальных конечных точках
Здесь у вас есть возможность запросить OTP после первоначальной аутентификации пользователя (или даже при обнаружении подозрительного устройства). Если OTP запрашивается, пользователю не нужно вводить пароль повторно, а также пароль нигде не хранится и не передается повторно при любом последующем запросе. Уровень безопасности остается таким же, как и при одновременной аутентификации <username, password, otp>
Обновить жетон
- Пользователь аутентифицируется и получает
renew_jwt
(срок действия истекает через длительный период или никогда) - Пользователь обменивает
renew_jwt
наauth_jwt
(быстро истекает) - Ваше приложение принимает только
auth_jwt
на всех остальных конечных точках
Это решение обеспечивает большую гибкость, если вы хотите, чтобы аутентификация была отменена или изменена (изменение ролей). Вы можете иметь auth_jwt
, который используется во всем приложении, но срок его действия истекает очень быстро, затем у вас есть renew_jwt
, который уже был проверен на правильность аутентификации и может быть использован для автоматического запроса auth_jwt
, этот запрос будет удовлетворен системой только при соблюдении определенных критериев (например, аутентификация не была отозвана).
Смешивание всего вместе
- Пользователь дает вам
<username, password, otp>
, он получаетauth_jwt
(илиrenew_jwt
) иotp_jwt
. - Пользователь дает вам
<username, password, otp_jwt>
.
- Ваше приложение принимает только
auth_jwt
на всех остальных конечных точках .
После первой аутентификации пользовательское устройство получает otp_jwt
(с произвольно большой продолжительностью), который может храниться на устройстве (cookies, appStorage, secureStorage, ...). Этот otp_jwt
может быть использован в любой момент в будущем (вместе с именем пользователя и паролем) для повторной аутентификации.
При условии, что устройство можно считать достаточно безопасным, такая стратегия не сильно увеличивает риск несанкционированного доступа. Но следует помнить, что вы всегда должны предоставлять пользователю возможность completely log out
например, очистить этот токен.
Что вы можете сделать здесь: всякий раз, когда вы создаете JWT-токен и устанавливаете его на устройстве пользователя для домена, вместе с ним создайте уникальный ключ (просто идентификатор для этого устройства) и установите его на устройстве пользователя как cookie для этого домена. (сохраните этот уникальный ключ и карту id пользователя в базе данных)
Установите срок действия этой cookie отличным от срока действия JWT и не очищайте эту cookie, даже если пользователь вышел из системы. (пусть срок действия истекает сам по себе)
Таким образом, даже если пользователь выйдет из устройства или срок действия jwt истечет, в следующем запросе на вход вы все равно получите cookie устройства, который вы установили (допустим, вы установили его на месяц). Когда приходит запрос на вход и в нем есть куки устройства, вы можете проверить, зарегистрирован ли у вас этот идентификатор пользователя и ключ устройства. Если да, вы знаете, что этот пользователь входил в систему с этого устройства в течение последнего месяца, и можете пропустить 2FA, в противном случае, если такой комбинации не существует, вы можете запросить 2FA.
Дополнительно допустим, что пользователь входит в систему впервые, в этом случае cookie устройства будет отсутствовать в запросе, и вы можете смело запрашивать 2FA.
вы можете играть с проверками этого куки устройства, например, вы также можете сохранить метку времени, когда был создан этот ключ, и увеличить уровень проверок.