|
8 | 8 | from django.utils.deprecation import MiddlewareMixin
|
9 | 9 | from django.utils.functional import cached_property
|
10 | 10 | from django.utils.module_loading import import_string
|
| 11 | +import requests |
11 | 12 |
|
12 |
| -from mozilla_django_oidc.auth import OIDCAuthenticationBackend |
| 13 | +from mozilla_django_oidc.auth import OIDCAuthenticationBackend, store_tokens |
13 | 14 | from mozilla_django_oidc.utils import (absolutify,
|
14 | 15 | add_state_and_nonce_to_session,
|
15 | 16 | import_from_settings)
|
@@ -122,16 +123,24 @@ def is_refreshable_url(self, request):
|
122 | 123 | not any(pat.match(request.path) for pat in self.exempt_url_patterns)
|
123 | 124 | )
|
124 | 125 |
|
125 |
| - def process_request(self, request): |
| 126 | + |
| 127 | + def is_expired(self, request): |
126 | 128 | if not self.is_refreshable_url(request):
|
127 | 129 | LOGGER.debug('request is not refreshable')
|
128 |
| - return |
| 130 | + return False |
129 | 131 |
|
130 |
| - expiration = request.session.get('oidc_id_token_expiration', 0) |
| 132 | + expiration = request.session.get('oidc_token_expiration', 0) |
131 | 133 | now = time.time()
|
132 | 134 | if expiration > now:
|
133 | 135 | # The id_token is still valid, so we don't have to do anything.
|
134 | 136 | LOGGER.debug('id token is still valid (%s > %s)', expiration, now)
|
| 137 | + return False |
| 138 | + |
| 139 | + return True |
| 140 | + |
| 141 | + def process_request(self, request): |
| 142 | + |
| 143 | + if not self.is_expired(request): |
135 | 144 | return
|
136 | 145 |
|
137 | 146 | LOGGER.debug('id token has expired')
|
@@ -179,3 +188,42 @@ def process_request(self, request):
|
179 | 188 | response['refresh_url'] = redirect_url
|
180 | 189 | return response
|
181 | 190 | return HttpResponseRedirect(redirect_url)
|
| 191 | + |
| 192 | + |
| 193 | +class RefreshOIDCAccessToken(SessionRefresh): |
| 194 | + """ |
| 195 | + A middleware that will refresh the access token following proper OIDC protocol: |
| 196 | + https://auth0.com/docs/tokens/refresh-token/current |
| 197 | + """ |
| 198 | + def process_request(self, request): |
| 199 | + if not self.is_expired(request): |
| 200 | + return |
| 201 | + |
| 202 | + token_url = import_from_settings('OIDC_OP_TOKEN_ENDPOINT') |
| 203 | + client_id = import_from_settings('OIDC_RP_CLIENT_ID') |
| 204 | + client_secret = import_from_settings('OIDC_RP_CLIENT_SECRET') |
| 205 | + refresh_token = request.session.get('oidc_refresh_token') |
| 206 | + if not refresh_token: |
| 207 | + LOGGER.debug('no refresh token stored') |
| 208 | + return |
| 209 | + |
| 210 | + token_payload = { |
| 211 | + 'grant_type': 'refresh_token', |
| 212 | + 'client_id': client_id, |
| 213 | + 'client_secret': client_secret, |
| 214 | + 'refresh_token': refresh_token, |
| 215 | + } |
| 216 | + |
| 217 | + response = requests.post(token_url, |
| 218 | + data=token_payload, |
| 219 | + verify=import_from_settings('OIDC_VERIFY_SSL', True)) |
| 220 | + response.raise_for_status() |
| 221 | + |
| 222 | + token_info = response.json() |
| 223 | + # Until we can properly validate an ID token on the refresh response |
| 224 | + # per the spec[1], we intentionally drop the id_token. |
| 225 | + # [1]: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse |
| 226 | + id_token = None |
| 227 | + access_token = token_info.get('access_token') |
| 228 | + refresh_token = token_info.get('refresh_token') |
| 229 | + store_tokens(request.session, access_token, id_token, refresh_token) |
0 commit comments