Skip to content

Commit eb623a8

Browse files
authored
Make authlib logout_uri more flexible (#107)
1 parent bfe72c7 commit eb623a8

File tree

3 files changed

+41
-12
lines changed

3 files changed

+41
-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: 34 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
@@ -21,6 +21,8 @@
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')
2323

24+
_notset = object()
25+
2426

2527
class _MultipassFlaskIntegration(FlaskIntegration):
2628
@staticmethod
@@ -70,6 +72,11 @@ class AuthlibAuthProvider(AuthProvider):
7072
of ``register()`` in the
7173
`authlib docs <https://docs.authlib.org/en/latest/client/frameworks.html>`_
7274
for details.
75+
- ``logout_uri``: a custom URL to redirect to after logging out; can be set to
76+
``None`` to avoid using the URL from the OIDC metadata
77+
- ``logout_args``: the special argument types to include in the query string of
78+
the logout uri. defaults to
79+
``{'client_id', 'id_token_hint', 'post_logout_redirect_uri'}``
7380
- ``request_timeout``: the timeout in seconds for fetching the oauth token and
7481
requesting data from the userinfo endpoint (10 by default,
7582
set to None to disable)
@@ -82,6 +89,8 @@ def __init__(self, *args, **kwargs):
8289
self.include_token = self.settings.get('include_token', False)
8390
self.request_timeout = self.settings.get('request_timeout')
8491
self.use_id_token = self.settings.get('use_id_token')
92+
self.logout_uri = self.settings.get('logout_uri', self.authlib_settings.get('logout_uri', _notset))
93+
self.logout_args = self.settings.get('logout_args', {'client_id', 'id_token_hint', 'post_logout_redirect_uri'})
8594
if self.use_id_token is None:
8695
# default to using the id token when using the openid scope (oidc)
8796
client_kwargs = self.authlib_settings.get('client_kwargs', {})
@@ -95,6 +104,10 @@ def __init__(self, *args, **kwargs):
95104
def authlib_settings(self):
96105
return self.settings['authlib_args']
97106

107+
@property
108+
def _id_token_key(self):
109+
return f'_multipass_authlib_id_token:{self.name}'
110+
98111
def _get_redirect_uri(self):
99112
return url_for(self.authorized_endpoint, _external=True)
100113

@@ -107,15 +120,24 @@ def initiate_external_login(self):
107120
return self.multipass.handle_auth_error(multipass_exc, True)
108121

109122
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)
123+
logout_uri = (
124+
self.authlib_client.load_server_metadata().get('end_session_endpoint')
125+
if self.logout_uri is _notset
126+
else self.logout_uri
127+
)
128+
if not logout_uri:
129+
return
130+
return_url = urljoin(request.url_root, return_url)
131+
client_id = self.authlib_settings['client_id']
132+
query_args = {}
133+
if 'client_id' in self.logout_args:
134+
query_args['client_id'] = client_id
135+
if 'post_logout_redirect_uri' in self.logout_args:
136+
query_args['post_logout_redirect_uri'] = return_url
137+
if (id_token := session.pop(self._id_token_key, None)) and 'id_token_hint' in self.logout_args:
138+
query_args['id_token_hint'] = id_token
139+
query = urlencode(query_args)
140+
return redirect((logout_uri + '?' + query) if query else logout_uri)
119141

120142
@login_view
121143
def _authorize_callback(self):
@@ -139,6 +161,8 @@ def _authorize_callback(self):
139161
logging.getLogger('multipass.authlib').error(f'Getting token failed: {error}: %s', desc)
140162
raise
141163
authinfo_token_data = {}
164+
if (id_token := token_data.get('id_token')) and 'id_token_hint' in self.logout_args:
165+
session[self._id_token_key] = id_token
142166
if self.include_token == 'only': # noqa: S105
143167
return self.multipass.handle_auth_success(AuthInfo(self, token=token_data))
144168
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)