From ed3abbb306b6ce87ed4909866c3ba46ed8f28ffe Mon Sep 17 00:00:00 2001 From: lilly Date: Fri, 27 Dec 2024 17:42:41 +0100 Subject: [PATCH] tighten login csrf protection by using a login timeout --- src/simple_openid_connect/integrations/django/apps.py | 3 +++ .../integrations/django/views.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/simple_openid_connect/integrations/django/apps.py b/src/simple_openid_connect/integrations/django/apps.py index 840ea51..5634015 100644 --- a/src/simple_openid_connect/integrations/django/apps.py +++ b/src/simple_openid_connect/integrations/django/apps.py @@ -54,6 +54,9 @@ class SettingsModel(BaseModel): ) "A string specifying a class that inherits from :class:`simple_openid_connect.integrations.django.user_mapping.UserMapper`." + OPENID_LOGIN_TIMEOUT: int = 60 * 5 + "Time in seconds which a login procedure is allowed to take at maximum. If a user takes more than this time between initiating a login and completing it, the login process fails and they have to redo it." + class OpenidAppConfig(AppConfig): """ diff --git a/src/simple_openid_connect/integrations/django/views.py b/src/simple_openid_connect/integrations/django/views.py index 9158019..83ef87c 100644 --- a/src/simple_openid_connect/integrations/django/views.py +++ b/src/simple_openid_connect/integrations/django/views.py @@ -3,6 +3,7 @@ """ import logging import secrets +from datetime import UTC, datetime, timedelta from http import HTTPStatus from typing import Mapping @@ -69,7 +70,7 @@ def get(self, request: HttpRequest) -> HttpResponse: # save the login state into the session to prevent CSRF attacks (openid state parameter could be used instead) # See https://www.rfc-editor.org/rfc/rfc6749#section-10.12 - request.session["openid_auth_in_progress"] = True + request.session["openid_auth_start_time"] = datetime.now(tz=UTC).timestamp() # prevent replay attacks by generating and specifying a nonce nonce = secrets.token_urlsafe(48) @@ -93,13 +94,17 @@ class LoginCallbackView(View): """ def get(self, request: HttpRequest) -> HttpResponse: + app_settings = OpenidAppConfig.get_instance().safe_settings client = OpenidAppConfig.get_instance().get_client(request) # prevent CSRF attacks by verifying that the user agent is curently in the process of authenticating - if request.session.get("openid_auth_in_progress", False) is not True: + if request.session.get("openid_auth_start_time", None) is None or ( + datetime.now(tz=UTC) + - datetime.fromtimestamp(request.session["openid_auth_start_time"], tz=UTC) + ) > timedelta(seconds=app_settings.OPENID_LOGIN_TIMEOUT): raise InvalidAuthStateError() else: - del request.session["openid_auth_in_progress"] + del request.session["openid_auth_start_time"] # exchange the passed code for tokens token_response = client.authorization_code_flow.handle_authentication_result(