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.

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

Вернуться на верх