9
9
10
10
from authlib .common .errors import AuthlibBaseError
11
11
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
13
13
from requests .exceptions import HTTPError , RequestException , Timeout
14
14
15
15
from flask_multipass .auth import AuthProvider
21
21
# jwt/oidc-specific fields that are not relevant to applications
22
22
INTERNAL_FIELDS = ('nonce' , 'session_state' , 'acr' , 'jti' , 'exp' , 'azp' , 'iss' , 'iat' , 'auth_time' , 'typ' , 'nbf' , 'aud' )
23
23
24
+ _notset = object ()
25
+
24
26
25
27
class _MultipassFlaskIntegration (FlaskIntegration ):
26
28
@staticmethod
@@ -70,6 +72,11 @@ class AuthlibAuthProvider(AuthProvider):
70
72
of ``register()`` in the
71
73
`authlib docs <https://docs.authlib.org/en/latest/client/frameworks.html>`_
72
74
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'}``
73
80
- ``request_timeout``: the timeout in seconds for fetching the oauth token and
74
81
requesting data from the userinfo endpoint (10 by default,
75
82
set to None to disable)
@@ -82,6 +89,8 @@ def __init__(self, *args, **kwargs):
82
89
self .include_token = self .settings .get ('include_token' , False )
83
90
self .request_timeout = self .settings .get ('request_timeout' )
84
91
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' })
85
94
if self .use_id_token is None :
86
95
# default to using the id token when using the openid scope (oidc)
87
96
client_kwargs = self .authlib_settings .get ('client_kwargs' , {})
@@ -95,6 +104,10 @@ def __init__(self, *args, **kwargs):
95
104
def authlib_settings (self ):
96
105
return self .settings ['authlib_args' ]
97
106
107
+ @property
108
+ def _id_token_key (self ):
109
+ return f'_multipass_authlib_id_token:{ self .name } '
110
+
98
111
def _get_redirect_uri (self ):
99
112
return url_for (self .authorized_endpoint , _external = True )
100
113
@@ -107,15 +120,24 @@ def initiate_external_login(self):
107
120
return self .multipass .handle_auth_error (multipass_exc , True )
108
121
109
122
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 )
119
141
120
142
@login_view
121
143
def _authorize_callback (self ):
@@ -139,6 +161,8 @@ def _authorize_callback(self):
139
161
logging .getLogger ('multipass.authlib' ).error (f'Getting token failed: { error } : %s' , desc )
140
162
raise
141
163
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
142
166
if self .include_token == 'only' : # noqa: S105
143
167
return self .multipass .handle_auth_success (AuthInfo (self , token = token_data ))
144
168
elif self .include_token :
0 commit comments