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(