Skip to content

Conversation

PeshekDotDev
Copy link
Contributor

Details

Docs update for single logout


Checklist

  • Local tests pass (ak test authentik/)
  • The code has been formatted (make lint-fix)

If an API change has been made

  • The API schema has been updated (make gen-build)

If changes to the frontend have been made

  • The code has been formatted (make web)

If applicable

  • The documentation has been updated
  • The documentation has been formatted (make docs)

@PeshekDotDev PeshekDotDev requested a review from a team as a code owner October 1, 2025 08:15
Copy link

netlify bot commented Oct 1, 2025

Deploy Preview for authentik-storybook canceled.

Name Link
🔨 Latest commit 475c321
🔍 Latest deploy log https://app.netlify.com/projects/authentik-storybook/deploys/68de4925890a1100081160c5

Copy link

netlify bot commented Oct 1, 2025

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit 475c321
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/68de4925cf101e0008ace66f
😎 Deploy Preview https://deploy-preview-17169--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

netlify bot commented Oct 1, 2025

Deploy Preview for authentik-integrations ready!

Name Link
🔨 Latest commit 475c321
🔍 Latest deploy log https://app.netlify.com/projects/authentik-integrations/deploys/68de4925fe305d0008845d06
😎 Deploy Preview https://deploy-preview-17169--authentik-integrations.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

codecov bot commented Oct 1, 2025

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
2112 2 2110 2
View the top 2 failed test(s) by shortest run time
tests.e2e.test_provider_ldap.TestProviderLDAP::test_ldap_bind_search
Stack Traces | 12.6s run time
self = <unittest.case._Outcome object at 0x7f0a0efccf30>
test_case = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>
result = <TestCaseFunction test_ldap_bind_search>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:651: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>
method = <bound method TestProviderLDAP.test_ldap_bind_search of <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:606: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
>           return func(self, *args, **kwargs)

tests/e2e/utils.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>,)
kwargs = {}, file = 'default/flow-default-invalidation-flow.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Invalidation flow\nentries:\n- attrs:\n    designation: invalidation\n    na...0\n    stage: !KeyOf default-invalidation-logout\n    target: !KeyOf flow\n  model: authentik_flows.flowstagebinding\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>,)
kwargs = {}, config = <AuthentikTenantsConfig: authentik_tenants>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>,)
kwargs = {}, config = <AuthentikOutpostConfig: authentik_outposts>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @reconcile_app("authentik_tenants")
    @reconcile_app("authentik_outposts")
    def test_ldap_bind_search(self):
        """Test simple bind + search"""
        # Remove akadmin to ensure list is correct
        # Remove user before starting container so it's not cached
        User.objects.filter(username="akadmin").delete()
    
        outpost = self._prepare()
        server = Server("ldap://localhost:3389", get_info=ALL)
        _connection = Connection(
            server,
            raise_exceptions=True,
            user=f"cn={self.user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
            password=self.user.username,
        )
        _connection.bind()
        self.assertTrue(
            Event.objects.filter(
                action=EventAction.LOGIN,
                user={
                    "pk": self.user.pk,
                    "email": self.user.email,
                    "username": self.user.username,
                },
            )
        )
    
        embedded_account = Outpost.objects.filter(managed=MANAGED_OUTPOST).first().user
    
        _connection.search(
            "ou=Users,DC=ldaP,dc=goauthentik,dc=io",
            "(objectClass=user)",
            search_scope=SUBTREE,
            attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES],
        )
        response: list = _connection.response
        # Remove raw_attributes to make checking easier
        for obj in response:
            del obj["raw_attributes"]
            del obj["raw_dn"]
            obj["attributes"] = dict(obj["attributes"])
        o_user = outpost.user
        expected = [
            {
                "dn": f"cn={o_user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
                "attributes": {
                    "cn": o_user.username,
                    "sAMAccountName": o_user.username,
                    "uid": o_user.uid,
                    "name": o_user.name,
                    "displayName": o_user.name,
                    "sn": o_user.name,
                    "mail": "",
                    "objectClass": [
                        "top",
                        "person",
                        "organizationalPerson",
                        "inetOrgPerson",
                        "user",
                        "posixAccount",
                        "goauthentik.io/ldap/user",
                    ],
                    "uidNumber": 2000 + o_user.pk,
                    "gidNumber": 2000 + o_user.pk,
                    "memberOf": [],
                    "homeDirectory": f"/home/{o_user.username}",
                    "ak-active": True,
                    "ak-superuser": False,
                    "pwdChangedTime": o_user.password_change_date.replace(microsecond=0),
                    "createTimestamp": o_user.date_joined.replace(microsecond=0),
                    "modifyTimestamp": o_user.last_updated.replace(microsecond=0),
                },
                "type": "searchResEntry",
            },
            {
                "dn": f"cn={embedded_account.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
                "attributes": {
                    "cn": embedded_account.username,
                    "sAMAccountName": embedded_account.username,
                    "uid": embedded_account.uid,
                    "name": embedded_account.name,
                    "displayName": embedded_account.name,
                    "sn": embedded_account.name,
                    "mail": "",
                    "objectClass": [
                        "top",
                        "person",
                        "organizationalPerson",
                        "inetOrgPerson",
                        "user",
                        "posixAccount",
                        "goauthentik.io/ldap/user",
                    ],
                    "uidNumber": 2000 + embedded_account.pk,
                    "gidNumber": 2000 + embedded_account.pk,
                    "memberOf": [],
                    "homeDirectory": f"/home/{embedded_account.username}",
                    "ak-active": True,
                    "ak-superuser": False,
                    "pwdChangedTime": embedded_account.password_change_date.replace(microsecond=0),
                    "createTimestamp": embedded_account.date_joined.replace(microsecond=0),
                    "modifyTimestamp": embedded_account.last_updated.replace(microsecond=0),
                },
                "type": "searchResEntry",
            },
            {
                "dn": f"cn={self.user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
                "attributes": {
                    "cn": self.user.username,
                    "sAMAccountName": self.user.username,
                    "uid": self.user.uid,
                    "name": self.user.name,
                    "displayName": self.user.name,
                    "sn": self.user.name,
                    "mail": self.user.email,
                    "objectClass": [
                        "top",
                        "person",
                        "organizationalPerson",
                        "inetOrgPerson",
                        "user",
                        "posixAccount",
                        "goauthentik.io/ldap/user",
                    ],
                    "uidNumber": 2000 + self.user.pk,
                    "gidNumber": 2000 + self.user.pk,
                    "memberOf": [
                        f"cn={group.name},ou=groups,dc=ldap,dc=goauthentik,dc=io"
                        for group in self.user.ak_groups.all()
                    ],
                    "homeDirectory": f"/home/{self.user.username}",
                    "ak-active": True,
                    "ak-superuser": True,
                    "extraAttribute": ["bar"],
                    "pwdChangedTime": self.user.password_change_date.replace(microsecond=0),
                    "createTimestamp": self.user.date_joined.replace(microsecond=0),
                    "modifyTimestamp": self.user.last_updated.replace(microsecond=0),
                },
                "type": "searchResEntry",
            },
        ]
>       self.assert_list_dict_equal(expected, response)

tests/e2e/test_provider_ldap.py:317: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>
expected = [{'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': 'ak-outpost-2bb2740bbce84377bbbd7d09fd4b282c', 'creat....timezone.utc), ...}, 'dn': 'cn=qawIl6rIih3Rk5Rvqxmy,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}]
actual = [{'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': 'ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4', 'displ...Iih3Rk5Rvqxmy', ...}, 'dn': 'cn=qawIl6rIih3Rk5Rvqxmy,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}]
match_key = 'dn'

    def assert_list_dict_equal(self, expected: list[dict], actual: list[dict], match_key="dn"):
        """Assert a list of dictionaries is identical, ignoring the ordering of items"""
        self.assertEqual(len(expected), len(actual))
        for res_item in actual:
            all_matching = [x for x in expected if x[match_key] == res_item[match_key]]
            self.assertEqual(len(all_matching), 1)
            matching = all_matching[0]
>           self.assertDictEqual(res_item, matching)

tests/e2e/test_provider_ldap.py:406: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>
d1 = {'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': 'ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4', 'displa...'dn': 'cn=ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}
d2 = {'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': 'ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4', 'create...'dn': 'cn=ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}
msg = None

    def assertDictEqual(self, d1, d2, msg=None):
        self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
        self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
    
        if d1 != d2:
            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
            diff = ('\n' + '\n'.join(difflib.ndiff(
                           pprint.pformat(d1).splitlines(),
                           pprint.pformat(d2).splitlines())))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:1206: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search>
msg = "{'dn'[101 chars]': {'displayName': 'Outpost authentik Embedded[668 chars]try'} != {'dn'[101 chars]': {'cn': 'ak-outpo... 'cn=ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4,ou=users,dc=ldap,dc=goauthentik,dc=io',\n   'type': 'searchResEntry'}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: {'dn'[101 chars]': {'displayName': 'Outpost authentik Embedded[668 chars]try'} != {'dn'[101 chars]': {'cn': 'ak-outpost-1fb85f1bf88b4d15a1ce75d0[946 chars]try'}
E         {'attributes': {'ak-active': True,
E                         'ak-superuser': False,
E                         'cn': 'ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4',
E       +                 'createTimestamp': datetime.datetime(2025, 10, 2, 19, 59, 35, tzinfo=datetime.timezone.utc),
E                         'displayName': 'Outpost authentik Embedded Outpost '
E                                        'Service-Account',
E                         'gidNumber': 2025,
E                         'homeDirectory': '/home/ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4',
E                         'mail': '',
E                         'memberOf': [],
E       +                 'modifyTimestamp': datetime.datetime(2025, 10, 2, 19, 59, 35, tzinfo=datetime.timezone.utc),
E                         'name': 'Outpost authentik Embedded Outpost Service-Account',
E                         'objectClass': ['top',
E                                         'person',
E                                         'organizationalPerson',
E                                         'inetOrgPerson',
E                                         'user',
E                                         'posixAccount',
E                                         'goauthentik.io/ldap/user'],
E       +                 'pwdChangedTime': datetime.datetime(2025, 10, 2, 19, 59, 35, tzinfo=datetime.timezone.utc),
E                         'sAMAccountName': 'ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4',
E                         'sn': 'Outpost authentik Embedded Outpost Service-Account',
E                         'uid': '7437864af69f7179081cacb6c080d6e451635fad89275384384d3dc21ef8e78c',
E                         'uidNumber': 2025},
E          'dn': 'cn=ak-outpost-1fb85f1bf88b4d15a1ce75d0f558dcd4,ou=users,dc=ldap,dc=goauthentik,dc=io',
E          'type': 'searchResEntry'}

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:732: AssertionError
tests.e2e.test_provider_ldap.TestProviderLDAP::test_ldap_bind_search_no_perms
Stack Traces | 13.2s run time
self = <unittest.case._Outcome object at 0x7f0a0f75d310>
test_case = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>
result = <TestCaseFunction test_ldap_bind_search_no_perms>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:651: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>
method = <bound method TestProviderLDAP.test_ldap_bind_search_no_perms of <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:606: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
>           return func(self, *args, **kwargs)

tests/e2e/utils.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>,)
kwargs = {}, file = 'default/flow-default-invalidation-flow.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Invalidation flow\nentries:\n- attrs:\n    designation: invalidation\n    na...0\n    stage: !KeyOf default-invalidation-logout\n    target: !KeyOf flow\n  model: authentik_flows.flowstagebinding\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>,)
kwargs = {}, config = <AuthentikTenantsConfig: authentik_tenants>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>,)
kwargs = {}, config = <AuthentikOutpostConfig: authentik_outposts>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @reconcile_app("authentik_tenants")
    @reconcile_app("authentik_outposts")
    def test_ldap_bind_search_no_perms(self):
        """Test simple bind + search"""
        user = create_test_user()
        self._prepare()
        server = Server("ldap://localhost:3389", get_info=ALL)
        _connection = Connection(
            server,
            raise_exceptions=True,
            user=f"cn={user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
            password=user.username,
        )
        _connection.bind()
        self.assertTrue(
            Event.objects.filter(
                action=EventAction.LOGIN,
                user={
                    "pk": user.pk,
                    "email": user.email,
                    "username": user.username,
                },
            )
        )
    
        _connection.search(
            "ou=Users,DC=ldaP,dc=goauthentik,dc=io",
            "(objectClass=user)",
            search_scope=SUBTREE,
            attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES],
        )
        response: list = _connection.response
        # Remove raw_attributes to make checking easier
        for obj in response:
            del obj["raw_attributes"]
            del obj["raw_dn"]
            obj["attributes"] = dict(obj["attributes"])
        expected = [
            {
                "dn": f"cn={user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
                "attributes": {
                    "cn": user.username,
                    "sAMAccountName": user.username,
                    "uid": user.uid,
                    "name": user.name,
                    "displayName": user.name,
                    "sn": user.name,
                    "mail": user.email,
                    "objectClass": [
                        "top",
                        "person",
                        "organizationalPerson",
                        "inetOrgPerson",
                        "user",
                        "posixAccount",
                        "goauthentik.io/ldap/user",
                    ],
                    "uidNumber": 2000 + user.pk,
                    "gidNumber": 2000 + user.pk,
                    "memberOf": [
                        f"cn={group.name},ou=groups,dc=ldap,dc=goauthentik,dc=io"
                        for group in user.ak_groups.all()
                    ],
                    "homeDirectory": f"/home/{user.username}",
                    "ak-active": True,
                    "ak-superuser": False,
                    "pwdChangedTime": user.password_change_date.replace(microsecond=0),
                    "createTimestamp": user.date_joined.replace(microsecond=0),
                    "modifyTimestamp": user.last_updated.replace(microsecond=0),
                },
                "type": "searchResEntry",
            },
        ]
>       self.assert_list_dict_equal(expected, response)

tests/e2e/test_provider_ldap.py:397: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>
expected = [{'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': '9hakLXi7lC4QNsWfPb2j', 'createTimestamp': datetime.d....timezone.utc), ...}, 'dn': 'cn=9hakLXi7lC4QNsWfPb2j,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}]
actual = [{'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': '9hakLXi7lC4QNsWfPb2j', 'displayName': '9hakLXi7lC4QNsWfPb2j', ...}, 'dn': 'cn=9hakLXi7lC4QNsWfPb2j,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}]
match_key = 'dn'

    def assert_list_dict_equal(self, expected: list[dict], actual: list[dict], match_key="dn"):
        """Assert a list of dictionaries is identical, ignoring the ordering of items"""
        self.assertEqual(len(expected), len(actual))
        for res_item in actual:
            all_matching = [x for x in expected if x[match_key] == res_item[match_key]]
            self.assertEqual(len(all_matching), 1)
            matching = all_matching[0]
>           self.assertDictEqual(res_item, matching)

tests/e2e/test_provider_ldap.py:406: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>
d1 = {'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': '9hakLXi7lC4QNsWfPb2j', 'displayName': '9hakLXi7lC4QNsWfPb2j', ...}, 'dn': 'cn=9hakLXi7lC4QNsWfPb2j,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}
d2 = {'attributes': {'ak-active': True, 'ak-superuser': False, 'cn': '9hakLXi7lC4QNsWfPb2j', 'createTimestamp': datetime.da...e.timezone.utc), ...}, 'dn': 'cn=9hakLXi7lC4QNsWfPb2j,ou=users,dc=ldap,dc=goauthentik,dc=io', 'type': 'searchResEntry'}
msg = None

    def assertDictEqual(self, d1, d2, msg=None):
        self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
        self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
    
        if d1 != d2:
            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
            diff = ('\n' + '\n'.join(difflib.ndiff(
                           pprint.pformat(d1).splitlines(),
                           pprint.pformat(d2).splitlines())))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:1206: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ldap.TestProviderLDAP testMethod=test_ldap_bind_search_no_perms>
msg = "{'dn'[78 chars]': {'mail': '9hakLXi7lC4QNsWfPb2j@goauthentik.[544 chars]try'} != {'dn'[78 chars]': {'cn': '9hakLXi7lC...mber': 2011},\n   'dn': 'cn=9hakLXi7lC4QNsWfPb2j,ou=users,dc=ldap,dc=goauthentik,dc=io',\n   'type': 'searchResEntry'}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: {'dn'[78 chars]': {'mail': '9hakLXi7lC4QNsWfPb2j@goauthentik.[544 chars]try'} != {'dn'[78 chars]': {'cn': '9hakLXi7lC4QNsWfPb2j', 'sAMAccountN[822 chars]try'}
E         {'attributes': {'ak-active': True,
E                         'ak-superuser': False,
E                         'cn': '9hakLXi7lC4QNsWfPb2j',
E       +                 'createTimestamp': datetime.datetime(2025, 10, 2, 19, 58, 46, tzinfo=datetime.timezone.utc),
E                         'displayName': '9hakLXi7lC4QNsWfPb2j',
E                         'gidNumber': 2011,
E                         'homeDirectory': '/home/9hakLXi7lC4QNsWfPb2j',
E                         'mail': '[email protected]',
E                         'memberOf': [],
E       +                 'modifyTimestamp': datetime.datetime(2025, 10, 2, 19, 58, 46, tzinfo=datetime.timezone.utc),
E                         'name': '9hakLXi7lC4QNsWfPb2j',
E                         'objectClass': ['top',
E                                         'person',
E                                         'organizationalPerson',
E                                         'inetOrgPerson',
E                                         'user',
E                                         'posixAccount',
E                                         'goauthentik.io/ldap/user'],
E       +                 'pwdChangedTime': datetime.datetime(2025, 10, 2, 19, 58, 46, tzinfo=datetime.timezone.utc),
E                         'sAMAccountName': '9hakLXi7lC4QNsWfPb2j',
E                         'sn': '9hakLXi7lC4QNsWfPb2j',
E                         'uid': '028da48b96b221d253c373ba83f8f06513137d443e0b12d2923d3753ee9ff820',
E                         'uidNumber': 2011},
E          'dn': 'cn=9hakLXi7lC4QNsWfPb2j,ou=users,dc=ldap,dc=goauthentik,dc=io',
E          'type': 'searchResEntry'}

.../hostedtoolcache/Python/3.13.7.............../x64/lib/python3.13/unittest/case.py:732: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Contributor

github-actions bot commented Oct 1, 2025

authentik PR Installation instructions

Instructions for docker-compose

Add the following block to your .env file:

AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=gh-14a82cff24be2c9223b9c3a49e7f9632f3c623da
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s

Afterwards, run the upgrade commands from the latest release notes.

Instructions for Kubernetes

Add the following block to your values.yml file:

authentik:
    outposts:
        container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
    image:
        repository: ghcr.io/goauthentik/dev-server
        tag: gh-14a82cff24be2c9223b9c3a49e7f9632f3c623da

Afterwards, run the upgrade commands from the latest release notes.

Copy link
Contributor

@dewi-tik dewi-tik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely awesome docs. Great job!


Single logout is a security feature that logs a user out of all their applications with active sessions when they log out of authentik. It uses the OAuth2/OpenID Connect front-channel and back-channel logout specifications in combination with SAML's Single Logout specification.

For example, if a user is concurrently logged into one OIDC provider and two SAML providers, when the user logs out of authentik, they will automatically be logged out of all three applications. Without SLO configured, users with active sessions across multiple providers would need to manually log out of each one individually.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For example, if a user is concurrently logged into one OIDC provider and two SAML providers, when the user logs out of authentik, they will automatically be logged out of all three applications. Without SLO configured, users with active sessions across multiple providers would need to manually log out of each one individually.
For example, if a user is concurrently logged into one OIDC provider and two SAML providers, when the user logs out of authentik, they will automatically be logged out of all three applications. Without SLO configured, users with active sessions across multiple providers would need to manually log out of each provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without SLO configured, users with active sessions across multiple providers would need to manually log out of each provider one at a time.

How does that feel?

Copy link
Contributor

@tanberry tanberry Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm they are logging out of applications, right? Not providers. Of course logging out of the app then triggers provider log out... But can we just say applications?

Maybe: "Without SLO configured, users with active sessions across multiple providers would need to manually log out of each application with a different provider one at a time."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change the whole paragraph to talk about applications since it's speaking in the context of the user, not authentik/providers.

Suggested change
For example, if a user is concurrently logged into one OIDC provider and two SAML providers, when the user logs out of authentik, they will automatically be logged out of all three applications. Without SLO configured, users with active sessions across multiple providers would need to manually log out of each one individually.
For example, if a user is concurrently logged into an OIDC application and two SAML applications, when the user logs out of authentik, they will automatically be logged out of all three applications. Without SLO configured, users with active sessions across multiple applications would need to manually log out of each one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works for me, with the slight discomfort that one is not logged into a provider... maybe "logged into an app via a SMAL provider"...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

For example, if a user is concurrently logged into one OIDC provider and two SAML providers, when the user logs out of authentik, they will automatically be logged out of all three applications. Without SLO configured, users with active sessions across multiple providers would need to manually log out of each one individually.

:::info
Check with your service provider to see if they support SAML Single Logout or OIDC front-channel/back-channel logout. Not all providers support these features.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Check with your service provider to see if they support SAML Single Logout or OIDC front-channel/back-channel logout. Not all providers support these features.
Check with your service provider to see if they support SAML Single Logout or OIDC front-channel/back-channel logout. Not all applications and services support these features.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question about the term "service"... do we absolutely have to introduce ithere, or would "provider" suffice? I'm reluctant to muddy the waters.. if we want to say "service" how can we clarify exactly what we mean? @dewi-tik

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all service providers support these features - how's that? I do have a similar concern of using applications and services because I stuck with using provider thoughout

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

@dewi-tik dewi-tik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely awesome docs. Great job and great features to add.

PeshekDotDev and others added 17 commits October 1, 2025 05:35
Co-authored-by: Dewi Roberts <[email protected]>
Signed-off-by: Connor Peshek <[email protected]>
Co-authored-by: Dewi Roberts <[email protected]>
Signed-off-by: Connor Peshek <[email protected]>
Co-authored-by: Dewi Roberts <[email protected]>
Signed-off-by: Connor Peshek <[email protected]>

## Logout flow injection

authentik dynamically injects logout stages into the user logout flow when provider sessions requiring logout are detected:
Copy link
Contributor

@tanberry tanberry Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@connor can we be more specific about the flow name? Or is it possibly a custom flow that we do not know the name of? We have the "Default Invalidation Flow"...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this statement applies to situations when SLO is not configured, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stages are kind of made to be "private". They are not configurable or customizable by the user and the user has no option of interacting with them or removing them

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I remember you told me about the multiple private stages.. but I am wondering about the phrase "the user logout flow ". What flow are we talking about here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user's active logout flow may be more appropriate. It's just whatever logout flow a user is going though. that could be the default logout flow, a custom logout flow, any flow that logs a user out

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User's current logout flow...? either that or your suggestion, thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your suggestion sounds great, done

@PeshekDotDev
Copy link
Contributor Author

Absolutely awesome docs. Great job and great features to add.

Thank you Dewi :)


1. Configure the SLS URL (Single Logout Service URL) - the provider's logout endpoint.
2. Select the SLS Binding (Redirect or POST).
3. Choose a Logout Method; front-channel iframe, front-channel native, or back-channel.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Logout Method also the UI label? If Yes, let's bold it.

And same question about "native".. if that is the redirect option, let's call it "native redirect" or some such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the redirect and post binding both happen within iframe and native, so calling it native redirect isn't as clean as calling it native. native will also handle post requests if desired, so it makes it sound exclusive to handling redirects. I know it's confusing from all the different wordings, but sticking with native is best for sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok cool about the rediret/native, but the terms "Logout Method".. is this a UI component? If yes please bold it, if not please lower-case it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, fixed

1. Configure the SLS URL (Single Logout Service URL) - the provider's logout endpoint.
2. Select the SLS Binding (Redirect or POST).
3. Choose a Logout Method; front-channel iframe, front-channel native, or back-channel.
4. Optionally, enable `Sign logout request` for additional security.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a UI label, should be bold....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

- **SAML**: Creates `SAMLSession` records containing the `SessionIndex`, `NameID`, and `NameID format` for each successful authentication.
- **OIDC**: Tracks session identifiers (`sid`) and ID tokens required for logout requests.

These session records are automatically created during authentication and cleaned up after logout or expiration.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we say "deleted" instead of cleaned up?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


## Administrative session termination

Back-channel logout is triggered even when a user session is terminated via administrative actions:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always? Just automatically.. ? If yes, maybe reword to something like: "Back-channel logout is always triggered when a user session is terminated via administrative actions:"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

3. For each logout method with active sessions, the appropriate logout stage is injected:
- **iframe logout stage** - Injected at index 1 (immediately after the logout stage) for front-channel iframe logout.
- **Native logout stage** - Injected at index 2 (after iframe logout, if present) for front-channel native logout.
- **Back-channel logout** - Executed server-side without injecting additional stages.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But only if SLO is configured, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, only if an application has been configured to have one of these 3 logout methods and has an active session. but you are unable to add a logout method (iframe, native, backchannel) if you dont have SLO configured for a provider. So that is implied here. The wording is from the perspective of the logout method, not the provider

Changing the sentence above this to the following might help clarify that

authentik dynamically injects logout stages into the user's current logout flow when provider sessions configured for Single Logout are detected:


This approach ensures that single logout happens automatically without requiring explicit flow configuration.

## Administrative session termination
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same section/content as in the single-_ogout.md file, right? Does it need to be here as well? It is specifically about back-channel and SLO, correct? I'd suggest that if you want to mention Administrative session termination, then replace this block with a sentence saying what it is, then link to the section in the single_logout.md file.

If it absolutely needs to be repeated here, we want to use a to pull it in to both places from a single source. I can point you to docs on how to do that or do it in a subsequent PR if you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it has to be in both places. It kind of made sense in either one, so i put it in both. But I think it makes more sense ot just be in slo instead of user_logout

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed


OAuth2/OpenID logout is a security feature defined in the OpenID Connect specification. It allows an OpenID Provider (OP), such as authentik, to notify Relying Parties (RPs) when a user session ends. This ensures that all associated applications can properly terminate the user's session.

For more information about single logout across all providers, see the [Single Logout (SLO) Overview](../../flows-stages/stages/single_logout.md).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link will have to be updated when you move the SLO docs to be under Providers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

1. In the Admin interface, navigate to **Applications** > **Providers**.
2. Edit or create an OAuth2 provider.
3. In the **Logout URI** field, enter the endpoint for logout (if supported by your RP).
4. Using the **Logout method** radio, select whether the RP supports **Front-channel logout** or **Back-channel logout** at that URI.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a toggle? Let's not use the word "radio"...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't a toggle. A toggle is enabled/disabled. This item renders as a radio, which is a multiple choice option select. Explicitly calling it a radio does feel odd though

"Using the logout method options"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Sorry, radio buttons! OK, sorry... you can just say:

"4. Select the Logout method to define whether the RP supports Front-channel logout or Back-channel logout at that URI."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's much better, fixed


### Logout URI format

The logout URI should be a single URL provided by your Relying Party application, for example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If "Logout URI" is a UI component, let's bold it and match capitaliztion of UI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

PeshekDotDev and others added 4 commits October 1, 2025 08:12
Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Connor Peshek <[email protected]>
Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Connor Peshek <[email protected]>
…backchannel_logout.mdx

Co-authored-by: Tana M Berry <[email protected]>
Signed-off-by: Connor Peshek <[email protected]>
Copy link
Contributor

@tanberry tanberry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this awesome documentation to go with your awesome feature, @PeshekDotDev !

I've finished reviewing, so after those and the other editors changes are in LGTM!


### Front-channel logout

With front-channel logout, authentik injects an iframe logout stage into the logout flow. This stage loads the Relying Party's front-channel logout URL in a hidden iframe within the user's browser. The logout URL includes session information as query parameters:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
With front-channel logout, authentik injects an iframe logout stage into the logout flow. This stage loads the Relying Party's front-channel logout URL in a hidden iframe within the user's browser. The logout URL includes session information as query parameters:
With front-channel logout, authentik injects an iframe logout stage into the logout flow. This stage loads the RP's (relying party) front-channel logout URL in a hidden iframe within the user's browser. The logout URL includes session information as query parameters:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants