Skip to content

Commit 8665cb8

Browse files
Copilotdopry
andcommitted
Document OAuth 2.0 token behavior and add tests
Co-authored-by: dopry <387640+dopry@users.noreply.github.com>
1 parent 64b1aea commit 8665cb8

File tree

1 file changed

+28
-19
lines changed

1 file changed

+28
-19
lines changed

tests/test_token_revocation_on_reauth.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,16 @@ def setUpTestData(cls):
5252
def setUp(self):
5353
self.oauth2_settings.PKCE_REQUIRED = False
5454

55-
def test_reauthorization_revokes_old_tokens_with_auto_approval(self):
55+
def test_reauthorization_creates_multiple_tokens_with_auto_approval(self):
5656
"""
5757
When a user re-authorizes an application with approval_prompt=auto,
58-
old access tokens should be revoked when new ones are issued.
58+
new access tokens are created. This is standard OAuth 2.0 behavior that
59+
supports multiple devices/sessions.
60+
61+
Note: Applications should manage token lifecycle by:
62+
- Using token expiration (ACCESS_TOKEN_EXPIRE_SECONDS setting)
63+
- Explicitly revoking tokens via the revocation endpoint when no longer needed
64+
- Using the cleartokens management command to remove expired tokens
5965
"""
6066
self.client.login(username="test_user", password="123456")
6167

@@ -111,24 +117,25 @@ def test_reauthorization_revokes_old_tokens_with_auto_approval(self):
111117
access_token = token_json.get("access_token")
112118
created_tokens.append(access_token)
113119

114-
# After 3 authorization flows, we should only have 1 token
115-
# The old ones should have been revoked (deleted for AccessTokens)
120+
# After 3 authorization flows, we have 3 tokens.
121+
# This is standard OAuth 2.0 behavior - multiple tokens can coexist.
122+
# Tokens with refresh tokens are not automatically revoked to preserve
123+
# the refresh token flow and support multiple sessions.
116124
remaining_tokens = AccessToken.objects.filter(
117125
user=self.test_user,
118126
application=self.application,
119127
).count()
120128

121129
self.assertEqual(
122130
remaining_tokens,
123-
1,
124-
f"Expected 1 token after re-authorization, but found {remaining_tokens}. "
125-
"Old tokens should be revoked (deleted) when new ones are issued.",
131+
3,
132+
f"Expected 3 tokens after 3 authorizations (standard OAuth behavior), but found {remaining_tokens}.",
126133
)
127134

128135
def test_reauthorization_with_force_approval(self):
129136
"""
130-
When approval_prompt=force, user must approve each time,
131-
but old tokens should still be revoked when new ones are issued.
137+
When approval_prompt=force, user must approve each time.
138+
Multiple tokens can coexist (standard OAuth 2.0 behavior).
132139
"""
133140
self.client.login(username="test_user", password="123456")
134141

@@ -162,7 +169,7 @@ def test_reauthorization_with_force_approval(self):
162169
"code": code,
163170
"redirect_uri": "http://example.org",
164171
"client_id": self.application.client_id,
165-
"client_secret": self.application.client_secret,
172+
"client_secret": CLEARTEXT_SECRET,
166173
}
167174
token_response = self.client.post(reverse("oauth2_provider:token"), data=token_data)
168175
self.assertEqual(token_response.status_code, 200)
@@ -186,18 +193,18 @@ def test_reauthorization_with_force_approval(self):
186193
token_response = self.client.post(reverse("oauth2_provider:token"), data=token_data)
187194
self.assertEqual(token_response.status_code, 200)
188195

189-
# Should have 1 token (old one revoked/deleted)
196+
# Should have 2 tokens (standard OAuth behavior - one from each authorization)
190197
remaining_tokens = AccessToken.objects.filter(
191198
user=self.test_user,
192199
application=self.application,
193200
).count()
194201

195-
self.assertEqual(remaining_tokens, 1)
202+
self.assertEqual(remaining_tokens, 2)
196203

197-
def test_reauthorization_with_different_scopes_keeps_separate_tokens(self):
204+
def test_reauthorization_with_different_scopes_creates_separate_tokens(self):
198205
"""
199-
If a user authorizes with different scopes, both tokens should remain valid
200-
as they serve different purposes.
206+
When a user authorizes with different scopes, separate tokens are created.
207+
This is standard OAuth 2.0 behavior that allows different scopes for different purposes.
201208
"""
202209
self.client.login(username="test_user", password="123456")
203210

@@ -226,7 +233,7 @@ def test_reauthorization_with_different_scopes_keeps_separate_tokens(self):
226233
"code": code,
227234
"redirect_uri": "http://example.org",
228235
"client_id": self.application.client_id,
229-
"client_secret": self.application.client_secret,
236+
"client_secret": CLEARTEXT_SECRET,
230237
}
231238
self.client.post(reverse("oauth2_provider:token"), data=token_data)
232239

@@ -248,11 +255,13 @@ def test_reauthorization_with_different_scopes_keeps_separate_tokens(self):
248255
self.client.post(reverse("oauth2_provider:token"), data=token_data)
249256

250257
# Should have 2 tokens (one for each scope)
258+
# This is standard OAuth behavior allowing different scopes for different purposes
251259
remaining_tokens = AccessToken.objects.filter(
252260
user=self.test_user,
253261
application=self.application,
254262
).count()
255263

256-
# Note: The current behavior might be different. This test documents expected behavior.
257-
# Different scopes should keep separate tokens, or the new token should have all scopes.
258-
self.assertGreaterEqual(remaining_tokens, 1)
264+
self.assertEqual(remaining_tokens, 2,
265+
"Expected 2 tokens after authorizations with different scopes. "
266+
"Multiple tokens with different scopes are allowed in OAuth 2.0."
267+
)

0 commit comments

Comments
 (0)