- Notifications
You must be signed in to change notification settings - Fork 824
Refresh Token Reuse Protection#1452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
n2ygk merged 4 commits into django-oauth:master from soerface:1404-refresh-token-reuse-protectionAug 13, 2024
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
4 commits Select commit Hold shift + click to select a range
de8010f Add tests which describe desired behaviour for REFRESH_TOKEN_REUSE_PR…
soerface 529ad80 Implement REFRESH_TOKEN_REUSE_PROTECTION (#1404)
soerface 975a6ea Update documentation
soerface 302da0d Add swappable dependency to REFRESH_TOKEN_MODEL
soerface File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions 19 oauth2_provider/migrations/0011_refreshtoken_token_family.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Generated by Django 5.2 on 2024-08-09 16:40 | ||
| from django.db import migrations, models | ||
| from oauth2_provider.settings import oauth2_settings | ||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ('oauth2_provider', '0010_application_allowed_origins'), | ||
| migrations.swappable_dependency(oauth2_settings.REFRESH_TOKEN_MODEL) | ||
| ] | ||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='refreshtoken', | ||
| name='token_family', | ||
| field=models.UUIDField(blank=True, editable=False, null=True), | ||
| ), | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -15,7 +15,6 @@ | ||
| from django.contrib.auth.hashers import check_password, identify_hasher | ||
| from django.core.exceptions import ObjectDoesNotExist | ||
| from django.db import transaction | ||
| from django.db.models import Q | ||
| from django.http import HttpRequest | ||
| from django.utils import dateformat, timezone | ||
| from django.utils.crypto import constant_time_compare | ||
| @@ -644,7 +643,9 @@ def save_bearer_token(self, token, request, *args, **kwargs): | ||
| source_refresh_token=refresh_token_instance, | ||
| ) | ||
| self._create_refresh_token(request, refresh_token_code, access_token) | ||
| self._create_refresh_token( | ||
| request, refresh_token_code, access_token, refresh_token_instance | ||
| ) | ||
| else: | ||
| # make sure that the token data we're returning matches | ||
| # the existing token | ||
| @@ -688,9 +689,17 @@ def _create_authorization_code(self, request, code, expires=None): | ||
| claims=json.dumps(request.claims or{}), | ||
| ) | ||
| def _create_refresh_token(self, request, refresh_token_code, access_token): | ||
| def _create_refresh_token(self, request, refresh_token_code, access_token, previous_refresh_token): | ||
| if previous_refresh_token: | ||
| token_family = previous_refresh_token.token_family | ||
| else: | ||
| token_family = uuid.uuid4() | ||
| return RefreshToken.objects.create( | ||
| user=request.user, token=refresh_token_code, application=request.client, access_token=access_token | ||
| user=request.user, | ||
| token=refresh_token_code, | ||
| application=request.client, | ||
| access_token=access_token, | ||
| token_family=token_family, | ||
| ) | ||
| def revoke_token(self, token, token_type_hint, request, *args, **kwargs): | ||
| @@ -752,22 +761,25 @@ def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs | ||
| Also attach User instance to the request object | ||
| """ | ||
| null_or_recent = Q(revoked__isnull=True) | Q( | ||
| revoked__gt=timezone.now() - timedelta(seconds=oauth2_settings.REFRESH_TOKEN_GRACE_PERIOD_SECONDS) | ||
| ) | ||
| rt = ( | ||
| RefreshToken.objects.filter(null_or_recent, token=refresh_token) | ||
| .select_related("access_token") | ||
| .first() | ||
| ) | ||
| rt = RefreshToken.objects.filter(token=refresh_token).select_related("access_token").first() | ||
n2ygk marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| if not rt: | ||
| return False | ||
| if rt.revoked is not None and rt.revoked <= timezone.now() - timedelta( | ||
| seconds=oauth2_settings.REFRESH_TOKEN_GRACE_PERIOD_SECONDS | ||
| ): | ||
| if oauth2_settings.REFRESH_TOKEN_REUSE_PROTECTION and rt.token_family: | ||
| rt_token_family = RefreshToken.objects.filter(token_family=rt.token_family) | ||
| for related_rt in rt_token_family.all(): | ||
| related_rt.revoke() | ||
| return False | ||
| request.user = rt.user | ||
| request.refresh_token = rt.token | ||
| # Temporary store RefreshToken instance to be reused by get_original_scopes and save_bearer_token. | ||
| request.refresh_token_instance = rt | ||
| return rt.application == client | ||
| @transaction.atomic | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -54,6 +54,7 @@ | ||
| "ID_TOKEN_EXPIRE_SECONDS": 36000, | ||
| "REFRESH_TOKEN_EXPIRE_SECONDS": None, | ||
| "REFRESH_TOKEN_GRACE_PERIOD_SECONDS": 0, | ||
| "REFRESH_TOKEN_REUSE_PROTECTION": False, | ||
n2ygk marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| "ROTATE_REFRESH_TOKEN": True, | ||
| "ERROR_RESPONSE_WITH_SCOPES": False, | ||
| "APPLICATION_MODEL": APPLICATION_MODEL, | ||
n2ygk marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Generated by Django 5.2 on 2024-08-09 16:40 | ||
| from django.db import migrations, models | ||
| from oauth2_provider.settings import oauth2_settings | ||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ('tests', '0005_basetestapplication_allowed_origins_and_more'), | ||
n2ygk marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| migrations.swappable_dependency(oauth2_settings.REFRESH_TOKEN_MODEL) | ||
| ] | ||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='samplerefreshtoken', | ||
| name='token_family', | ||
| field=models.UUIDField(blank=True, editable=False, null=True), | ||
| ), | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.