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
20
20
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
+ # flask session key to store the id token
24
+ ID_TOKEN_SESSION_KEY = '_multipass_authlib_id_token' # noqa: S105
25
+
26
+ _notset = object ()
23
27
24
28
25
29
class _MultipassFlaskIntegration (FlaskIntegration ):
@@ -70,6 +74,11 @@ class AuthlibAuthProvider(AuthProvider):
70
74
of ``register()`` in the
71
75
`authlib docs <https://docs.authlib.org/en/latest/client/frameworks.html>`_
72
76
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'}``
73
82
- ``request_timeout``: the timeout in seconds for fetching the oauth token and
74
83
requesting data from the userinfo endpoint (10 by default,
75
84
set to None to disable)
@@ -82,6 +91,8 @@ def __init__(self, *args, **kwargs):
82
91
self .include_token = self .settings .get ('include_token' , False )
83
92
self .request_timeout = self .settings .get ('request_timeout' )
84
93
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' })
85
96
if self .use_id_token is None :
86
97
# default to using the id token when using the openid scope (oidc)
87
98
client_kwargs = self .authlib_settings .get ('client_kwargs' , {})
@@ -107,15 +118,24 @@ def initiate_external_login(self):
107
118
return self .multipass .handle_auth_error (multipass_exc , True )
108
119
109
120
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 )
119
139
120
140
@login_view
121
141
def _authorize_callback (self ):
@@ -139,6 +159,8 @@ def _authorize_callback(self):
139
159
logging .getLogger ('multipass.authlib' ).error (f'Getting token failed: { error } : %s' , desc )
140
160
raise
141
161
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
142
164
if self .include_token == 'only' : # noqa: S105
143
165
return self .multipass .handle_auth_success (AuthInfo (self , token = token_data ))
144
166
elif self .include_token :
0 commit comments