Skip to content

Commit 7306f21

Browse files
committed
Add expiration for refresh token
1 parent 0fdf128 commit 7306f21

File tree

4 files changed

+88
-2
lines changed

4 files changed

+88
-2
lines changed

oidc_provider/lib/endpoints/token.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,15 @@ def validate_params(self):
122122
try:
123123
self.token = Token.objects.get(refresh_token=self.params['refresh_token'],
124124
client=self.client)
125-
126125
except Token.DoesNotExist:
127126
logger.debug(
128127
'[Token] Refresh token does not exist: %s', self.params['refresh_token'])
129128
raise TokenError('invalid_grant')
129+
130+
if self.token.has_expired_refresh_token():
131+
logger.debug(
132+
'[Token] Refresh token expired: %s', self.params['refresh_token'])
133+
raise TokenError('invalid_token')
130134
elif self.params['grant_type'] == 'client_credentials':
131135
if not self.client._scope:
132136
logger.debug('[Token] Client using client credentials with empty scope')

oidc_provider/models.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
import binascii
44
from hashlib import md5, sha256
55
import json
6+
import datetime
67

78
from django.db import models
89
from django.utils import timezone
910
from django.utils.translation import ugettext_lazy as _
1011
from django.conf import settings
11-
12+
from . import settings as oidc_settings
1213

1314
CLIENT_TYPE_CHOICES = [
1415
('confidential', 'Confidential'),
@@ -217,6 +218,24 @@ class Meta:
217218
def id_token(self):
218219
return json.loads(self._id_token) if self._id_token else None
219220

221+
def has_expired_refresh_token(self):
222+
if not oidc_settings.get('OIDC_REFRESH_TOKEN_EXPIRE'):
223+
return False
224+
225+
if oidc_settings.get('OIDC_REFRESH_TOKEN_EXPIRE') < oidc_settings.get('OIDC_TOKEN_EXPIRE'):
226+
raise ValueError('Invalid setting for OIDC_REFRESH_TOKEN_EXPIRE')
227+
228+
# Note: Increasing expiration time settings could make previously
229+
# expired refresh tokens usable. Therefore, clear all the
230+
# refresh tokens when increasing refresh token expire time.
231+
offset = (
232+
oidc_settings.get('OIDC_REFRESH_TOKEN_EXPIRE')
233+
- oidc_settings.get('OIDC_TOKEN_EXPIRE')
234+
)
235+
expires_at = self.expires_at + datetime.timedelta(seconds=offset)
236+
237+
return timezone.now() >= expires_at
238+
220239
@id_token.setter
221240
def id_token(self, value):
222241
self._id_token = json.dumps(value)

oidc_provider/settings.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ def OIDC_TOKEN_EXPIRE(self):
113113
"""
114114
return 60*60
115115

116+
@property
117+
def OIDC_REFRESH_TOKEN_EXPIRE(self):
118+
"""
119+
OPTIONAL. Refresh token expiration time expressed in seconds.
120+
121+
Zero: Refresh token doesn't expire.
122+
Note: Increasing expiration time settings could make previously
123+
expired refresh tokens usable. Hence, increase expiry time with care.
124+
Ex: delete existing refresh tokens before increasing
125+
refresh token expire time.
126+
"""
127+
return 0
128+
116129
@property
117130
def OIDC_USERINFO(self):
118131
"""

oidc_provider/tests/cases/test_token_endpoint.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import uuid
44

55
from base64 import b64encode
6+
from datetime import timedelta
7+
8+
from django.utils import timezone
69

710
try:
811
from urllib.parse import urlencode
@@ -429,6 +432,53 @@ def do_refresh_token_check(self, scope=None):
429432
response = self._post_request(post_data)
430433
self.assertIn('invalid_grant', response.content.decode('utf-8'))
431434

435+
# Refresh token doesn't expires with default settings
436+
post_data = self._refresh_token_post_data(response_dic2['refresh_token'], scope)
437+
expiry_time = timezone.now() + timedelta(
438+
seconds=24*60*60,
439+
)
440+
with patch('oidc_provider.models.timezone.now') as time_func:
441+
time_func.return_value = expiry_time
442+
response = self._post_request(post_data)
443+
self.assertIn('refresh_token', response.content.decode('utf-8'))
444+
445+
@override_settings(OIDC_IDTOKEN_INCLUDE_CLAIMS=True)
446+
@override_settings(OIDC_REFRESH_TOKEN_EXPIRE=24 * 60 * 60)
447+
def test_refresh_token_expiry(self):
448+
"""
449+
Refresh token expiry
450+
Make sure that refresh token expires properly.
451+
"""
452+
response = self._refresh_request(elapsed_time=24 * 60 * 60)
453+
self.assertIn('invalid_token', response.content.decode('utf-8'))
454+
455+
@override_settings(OIDC_IDTOKEN_INCLUDE_CLAIMS=True)
456+
@override_settings(OIDC_REFRESH_TOKEN_EXPIRE=24 * 60 * 60)
457+
def test_refresh_token_not_expired(self, scope=None):
458+
"""
459+
Refresh token expiry
460+
Make sure that refresh token is not expired.
461+
"""
462+
response = self._refresh_request(elapsed_time=(24 * 60 * 60) - 1)
463+
self.assertIn('id_token', response.content.decode('utf-8'))
464+
465+
def _refresh_request(self, elapsed_time):
466+
code = self._create_code()
467+
self.assertEqual(code.scope, ['openid', 'email'])
468+
post_data = self._auth_code_post_data(code=code.code)
469+
response = self._post_request(post_data)
470+
response_dic1 = json.loads(response.content.decode('utf-8'))
471+
# Use refresh token to obtain new token
472+
post_data = self._refresh_token_post_data(
473+
response_dic1['refresh_token'], code.scope
474+
)
475+
current_time = timezone.now() + timedelta(seconds=elapsed_time)
476+
with patch('oidc_provider.models.timezone.now') as time_func:
477+
time_func.return_value = current_time
478+
response = self._post_request(post_data)
479+
480+
return response
481+
432482
def test_client_redirect_uri(self):
433483
"""
434484
Validate that client redirect URIs exactly match registered

0 commit comments

Comments
 (0)