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..ace96c8 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 datetime, timedelta, timezone from http import HTTPStatus from typing import Mapping @@ -69,7 +70,9 @@ 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=timezone.utc + ).timestamp() # prevent replay attacks by generating and specifying a nonce nonce = secrets.token_urlsafe(48) @@ -93,13 +96,19 @@ 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: + # prevent CSRF attacks by verifying that the user agent is curently in the process of authenticating and that the authentication was not started more than the configured amount of time ago + if request.session.get("openid_auth_start_time", None) is None or ( + datetime.now(tz=timezone.utc) + - datetime.fromtimestamp( + request.session["openid_auth_start_time"], tz=timezone.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(