diff --git a/oauthenticator/auth0.py b/oauthenticator/auth0.py index 31b2fa27..d1e9d274 100644 --- a/oauthenticator/auth0.py +++ b/oauthenticator/auth0.py @@ -2,6 +2,7 @@ A JupyterHub authenticator class for use with Auth0 as an identity provider. """ import os +from urllib.parse import urlencode from jupyterhub.auth import LocalAuthenticator from traitlets import Unicode, default @@ -41,6 +42,17 @@ def _auth0_domain_default(self): "Configuring either auth0_domain or auth0_subdomain is required" ) + logout_redirect_to_url = Unicode( + config=True, + help=""" + Redirect to this URL after the user is logged out. + + Must be explicitly added to the "Allowed Logout URLs" in the configuration + for this Auth0 application. See https://auth0.com/docs/authenticate/login/logout/redirect-users-after-logout + for more information. + """ + ) + auth0_subdomain = Unicode( config=True, help=""" @@ -57,7 +69,13 @@ def _auth0_subdomain_default(self): @default("logout_redirect_url") def _logout_redirect_url_default(self): - return f"https://{self.auth0_domain}/v2/logout" + url = f"https://{self.auth0_domain}/v2/logout" + if self.logout_redirect_to_url: + # If a redirectTo is set, we must also include the `client_id` + # Auth0 expects `client_id` to be snake cased while `redirectTo` is camel cased + params = urlencode({"client_id": self.client_id, "redirectTo": self.logout_redirect_to_url}) + url = f"{url}?{params}" + return url @default("authorize_url") def _authorize_url_default(self): diff --git a/oauthenticator/tests/test_auth0.py b/oauthenticator/tests/test_auth0.py index e4ae3be9..c5e24a6b 100644 --- a/oauthenticator/tests/test_auth0.py +++ b/oauthenticator/tests/test_auth0.py @@ -96,8 +96,13 @@ async def test_auth0( assert auth_model == None -async def test_custom_logout(monkeypatch): +@mark.parametrize(("logout_redirect_to_url", "redirect_url"), [ + ("", f"https://{AUTH0_DOMAIN}/v2/logout"), + ("https://hub-url.com", f"https://{AUTH0_DOMAIN}/v2/logout?client_id=&redirectTo=https%3A%2F%2Fhub-url.com") +]) +async def test_custom_logout(monkeypatch, logout_redirect_to_url, redirect_url): authenticator = Auth0OAuthenticator() + authenticator.logout_redirect_to_url = logout_redirect_to_url logout_handler = mock_handler(OAuthLogoutHandler, authenticator=authenticator) monkeypatch.setattr(web.RequestHandler, 'redirect', Mock()) @@ -114,8 +119,7 @@ async def test_custom_logout(monkeypatch): # Check redirection to the custom logout url authenticator.auth0_domain = AUTH0_DOMAIN await logout_handler.get() - custom_logout_url = f'https://{AUTH0_DOMAIN}/v2/logout' - logout_handler.redirect.assert_called_with(custom_logout_url) + logout_handler.redirect.assert_called_with(redirect_url) @mark.parametrize(