authentik.providers.iframe_logout

Shared logout stages for SAML and OIDC providers

 1"""Shared logout stages for SAML and OIDC providers"""
 2
 3from django.http import HttpResponse
 4from rest_framework.fields import CharField, ListField
 5
 6from authentik.common.oauth.constants import PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS
 7from authentik.core.api.utils import PassiveSerializer
 8from authentik.flows.challenge import Challenge, ChallengeResponse
 9from authentik.flows.stage import ChallengeStageView
10from authentik.providers.saml.views.flows import PLAN_CONTEXT_SAML_LOGOUT_IFRAME_SESSIONS
11
12
13class LogoutURL(PassiveSerializer):
14    """Data for a single logout URL"""
15
16    url = CharField()
17    provider_name = CharField(required=False, allow_null=True)
18    binding = CharField(required=False, allow_null=True)
19    saml_request = CharField(required=False, allow_null=True)
20    saml_response = CharField(required=False, allow_null=True)
21    saml_relay_state = CharField(required=False, allow_null=True)
22
23
24class IframeLogoutChallenge(Challenge):
25    """Challenge for iframe logout"""
26
27    component = CharField(default="ak-provider-iframe-logout")
28    logout_urls = ListField(child=LogoutURL(), default=list)
29
30
31class IframeLogoutChallengeResponse(ChallengeResponse):
32    """Response for iframe logout"""
33
34    component = CharField(default="ak-provider-iframe-logout")
35
36
37class IframeLogoutStageView(ChallengeStageView):
38    """SAML and OIDC Logout stage that handles parallel iframe logout"""
39
40    response_class = IframeLogoutChallengeResponse
41
42    def get_challenge(self) -> Challenge:
43        """Generate iframe logout challenge for both SAML and OIDC"""
44        logout_urls = []
45
46        if self.executor.plan:
47            saml_sessions = self.executor.plan.context.get(
48                PLAN_CONTEXT_SAML_LOGOUT_IFRAME_SESSIONS, []
49            )
50            oidc_sessions = self.executor.plan.context.get(
51                PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS, []
52            )
53
54            logout_urls.extend(saml_sessions)
55            logout_urls.extend(oidc_sessions)
56
57            self.executor.plan.context.pop(PLAN_CONTEXT_SAML_LOGOUT_IFRAME_SESSIONS, None)
58            self.executor.plan.context.pop(PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS, None)
59        else:
60            saml_sessions = []
61            oidc_sessions = []
62
63        return IframeLogoutChallenge(
64            data={
65                "component": "ak-provider-iframe-logout",
66                "logout_urls": logout_urls,
67            }
68        )
69
70    def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
71        """Iframe logout completed"""
72        return self.executor.stage_ok()
class LogoutURL(authentik.core.api.utils.PassiveSerializer):
14class LogoutURL(PassiveSerializer):
15    """Data for a single logout URL"""
16
17    url = CharField()
18    provider_name = CharField(required=False, allow_null=True)
19    binding = CharField(required=False, allow_null=True)
20    saml_request = CharField(required=False, allow_null=True)
21    saml_response = CharField(required=False, allow_null=True)
22    saml_relay_state = CharField(required=False, allow_null=True)

Data for a single logout URL

url
provider_name
binding
saml_request
saml_response
saml_relay_state
class IframeLogoutChallenge(authentik.flows.challenge.Challenge):
25class IframeLogoutChallenge(Challenge):
26    """Challenge for iframe logout"""
27
28    component = CharField(default="ak-provider-iframe-logout")
29    logout_urls = ListField(child=LogoutURL(), default=list)

Challenge for iframe logout

component
logout_urls
class IframeLogoutChallengeResponse(authentik.flows.challenge.ChallengeResponse):
32class IframeLogoutChallengeResponse(ChallengeResponse):
33    """Response for iframe logout"""
34
35    component = CharField(default="ak-provider-iframe-logout")

Response for iframe logout

component
class IframeLogoutStageView(authentik.flows.stage.ChallengeStageView):
38class IframeLogoutStageView(ChallengeStageView):
39    """SAML and OIDC Logout stage that handles parallel iframe logout"""
40
41    response_class = IframeLogoutChallengeResponse
42
43    def get_challenge(self) -> Challenge:
44        """Generate iframe logout challenge for both SAML and OIDC"""
45        logout_urls = []
46
47        if self.executor.plan:
48            saml_sessions = self.executor.plan.context.get(
49                PLAN_CONTEXT_SAML_LOGOUT_IFRAME_SESSIONS, []
50            )
51            oidc_sessions = self.executor.plan.context.get(
52                PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS, []
53            )
54
55            logout_urls.extend(saml_sessions)
56            logout_urls.extend(oidc_sessions)
57
58            self.executor.plan.context.pop(PLAN_CONTEXT_SAML_LOGOUT_IFRAME_SESSIONS, None)
59            self.executor.plan.context.pop(PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS, None)
60        else:
61            saml_sessions = []
62            oidc_sessions = []
63
64        return IframeLogoutChallenge(
65            data={
66                "component": "ak-provider-iframe-logout",
67                "logout_urls": logout_urls,
68            }
69        )
70
71    def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
72        """Iframe logout completed"""
73        return self.executor.stage_ok()

SAML and OIDC Logout stage that handles parallel iframe logout

response_class = <class 'IframeLogoutChallengeResponse'>
def get_challenge(self) -> authentik.flows.challenge.Challenge:
43    def get_challenge(self) -> Challenge:
44        """Generate iframe logout challenge for both SAML and OIDC"""
45        logout_urls = []
46
47        if self.executor.plan:
48            saml_sessions = self.executor.plan.context.get(
49                PLAN_CONTEXT_SAML_LOGOUT_IFRAME_SESSIONS, []
50            )
51            oidc_sessions = self.executor.plan.context.get(
52                PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS, []
53            )
54
55            logout_urls.extend(saml_sessions)
56            logout_urls.extend(oidc_sessions)
57
58            self.executor.plan.context.pop(PLAN_CONTEXT_SAML_LOGOUT_IFRAME_SESSIONS, None)
59            self.executor.plan.context.pop(PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS, None)
60        else:
61            saml_sessions = []
62            oidc_sessions = []
63
64        return IframeLogoutChallenge(
65            data={
66                "component": "ak-provider-iframe-logout",
67                "logout_urls": logout_urls,
68            }
69        )

Generate iframe logout challenge for both SAML and OIDC

def challenge_valid( self, response: authentik.flows.challenge.ChallengeResponse) -> django.http.response.HttpResponse:
71    def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
72        """Iframe logout completed"""
73        return self.executor.stage_ok()

Iframe logout completed