Skip to content

Commit fdf7835

Browse files
committed
Make authlib logout_uri more flexible
1 parent bfe72c7 commit fdf7835

File tree

3 files changed

+39
-12
lines changed

3 files changed

+39
-12
lines changed

CHANGES.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ Version 0.8
55
-----------
66

77
- Reject ``next`` URLs containing linebreaks gracefully
8+
- Look for ``logout_uri`` in top-level authlib provider config instead of the
9+
``authlib_args`` dict (the latter is still checked as a fallback)
10+
- Include ``id_token_hint`` in authlib logout URL
11+
- Add ``logout_args`` setting to authlib provider which allows removing some of
12+
the query string arguments that are included by default
813

914
Version 0.7
1015
-----------
1116

1217
- Support multiple id fields in SAML identity provider
13-
- Include ``client_id`` in authlib logout URL since some OIDC providers mayrequire this
18+
- Include ``client_id`` in authlib logout URL since some OIDC providers may require this
1419
- Allow setting timeout for authlib token requests (default: 10 seconds)
1520
- Add new ``MULTIPASS_HIDE_NO_SUCH_USER`` config setting to convert ``NoSuchUser``
1621
exceptions to ``InvalidCredentials`` to avoid disclosing whether a username is valid

flask_multipass/providers/authlib.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from authlib.common.errors import AuthlibBaseError
1111
from authlib.integrations.flask_client import FlaskIntegration, OAuth
12-
from flask import current_app, redirect, request, url_for
12+
from flask import current_app, redirect, request, session, url_for
1313
from requests.exceptions import HTTPError, RequestException, Timeout
1414

1515
from flask_multipass.auth import AuthProvider
@@ -20,6 +20,10 @@
2020

2121
# jwt/oidc-specific fields that are not relevant to applications
2222
INTERNAL_FIELDS = ('nonce', 'session_state', 'acr', 'jti', 'exp', 'azp', 'iss', 'iat', 'auth_time', 'typ', 'nbf', 'aud')
23+
# flask session key to store the id token
24+
ID_TOKEN_SESSION_KEY = '_multipass_authlib_id_token' # noqa: S105
25+
26+
_notset = object()
2327

2428

2529
class _MultipassFlaskIntegration(FlaskIntegration):
@@ -70,6 +74,11 @@ class AuthlibAuthProvider(AuthProvider):
7074
of ``register()`` in the
7175
`authlib docs <https://docs.authlib.org/en/latest/client/frameworks.html>`_
7276
for details.
77+
- ``logout_uri``: a custom URL to redirect to after logging out; can be set to
78+
``None`` to avoid using the URL from the OIDC metadata
79+
- ``logout_args``: the special argument types to include in the query string of
80+
the logout uri. defaults to
81+
``{'client_id', 'id_token_hint', 'post_logout_redirect_uri'}``
7382
- ``request_timeout``: the timeout in seconds for fetching the oauth token and
7483
requesting data from the userinfo endpoint (10 by default,
7584
set to None to disable)
@@ -82,6 +91,8 @@ def __init__(self, *args, **kwargs):
8291
self.include_token = self.settings.get('include_token', False)
8392
self.request_timeout = self.settings.get('request_timeout')
8493
self.use_id_token = self.settings.get('use_id_token')
94+
self.logout_uri = self.settings.get('logout_uri', self.authlib_settings.get('logout_uri', _notset))
95+
self.logout_args = self.settings.get('logout_args', {'client_id', 'id_token_hint', 'post_logout_redirect_uri'})
8596
if self.use_id_token is None:
8697
# default to using the id token when using the openid scope (oidc)
8798
client_kwargs = self.authlib_settings.get('client_kwargs', {})
@@ -107,15 +118,24 @@ def initiate_external_login(self):
107118
return self.multipass.handle_auth_error(multipass_exc, True)
108119

109120
def process_logout(self, return_url):
110-
try:
111-
logout_uri = self.authlib_settings['logout_uri']
112-
except KeyError:
113-
logout_uri = self.authlib_client.load_server_metadata().get('end_session_endpoint')
114-
if logout_uri:
115-
return_url = urljoin(request.url_root, return_url)
116-
client_id = self.authlib_settings['client_id']
117-
query = urlencode({'post_logout_redirect_uri': return_url, 'client_id': client_id})
118-
return redirect(logout_uri + '?' + query)
121+
logout_uri = (
122+
self.authlib_client.load_server_metadata().get('end_session_endpoint')
123+
if self.logout_uri is _notset
124+
else self.logout_uri
125+
)
126+
if not logout_uri:
127+
return
128+
return_url = urljoin(request.url_root, return_url)
129+
client_id = self.authlib_settings['client_id']
130+
query_args = {}
131+
if 'client_id' in self.logout_args:
132+
query_args['client_id'] = client_id
133+
if 'post_logout_redirect_uri' in self.logout_args:
134+
query_args['post_logout_redirect_uri'] = return_url
135+
if (id_token := session.pop(ID_TOKEN_SESSION_KEY, None)) and 'id_token_hint' in self.logout_args:
136+
query_args['id_token_hint'] = id_token
137+
query = urlencode(query_args)
138+
return redirect((logout_uri + '?' + query) if query else logout_uri)
119139

120140
@login_view
121141
def _authorize_callback(self):
@@ -139,6 +159,8 @@ def _authorize_callback(self):
139159
logging.getLogger('multipass.authlib').error(f'Getting token failed: {error}: %s', desc)
140160
raise
141161
authinfo_token_data = {}
162+
if (id_token := token_data.get('id_token')) and 'id_token_hint' in self.logout_args:
163+
session[ID_TOKEN_SESSION_KEY] = id_token
142164
if self.include_token == 'only': # noqa: S105
143165
return self.multipass.handle_auth_success(AuthInfo(self, token=token_data))
144166
elif self.include_token:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = 'Flask-Multipass'
3-
version = '0.7'
3+
version = '0.8'
44
description = 'A pluggable solution for multi-backend authentication with Flask'
55
readme = 'README.rst'
66
license = 'BSD-3-Clause'

0 commit comments

Comments
 (0)