Skip to content

feat: add clear_expired_tokens task to remove oauth expired tokens#3835

Open
Anas12091101 wants to merge 8 commits intomasterfrom
anas/add-clear_expired_tokens-task
Open

feat: add clear_expired_tokens task to remove oauth expired tokens#3835
Anas12091101 wants to merge 8 commits intomasterfrom
anas/add-clear_expired_tokens-task

Conversation

@Anas12091101
Copy link
Contributor

What are the relevant tickets?

https://github.com/mitodl/hq/issues/10115

Description (What does it do?)

This PR adds clear_expired_tokens task to remove OAuth expired tokens. The task is set to run every week.

Screenshots (if appropriate):

  • Desktop screenshots
  • Mobile width screenshots

How can this be tested?

  • Create 1M access and refresh tokens using this management command: seed_expired_tokens.py
  • Run the command ./manage.py cleartokens and verify it deletes only expired tokens

Additional Context

@Anas12091101 Anas12091101 force-pushed the anas/add-clear_expired_tokens-task branch from 46b5174 to a4662cb Compare March 4, 2026 15:09
@Anas12091101 Anas12091101 force-pushed the anas/add-clear_expired_tokens-task branch from 39a54ac to 6af50ce Compare March 4, 2026 18:25
@Anas12091101
Copy link
Contributor Author

@dsubak, there are approximately ~1.8M access and refresh tokens in xPRO, and the COUNT(*) query returned almost instantly. Given that, it seems the table size isn’t causing performance issues.

Do you think we still need the chunked_oauth_access_token_delete.py script in this case?

@dsubak
Copy link

dsubak commented Mar 5, 2026

@dsubak, there are approximately ~1.8M access and refresh tokens in xPRO, and the COUNT(*) query returned almost instantly. Given that, it seems the table size isn’t causing performance issues.

Do you think we still need the chunked_oauth_access_token_delete.py script in this case?

@Anas12091101 I'd give a manual run of cleartokens a shot first. No need to use my weird script unless it doesn't succeed!

@arslanashraf7 arslanashraf7 self-assigned this Mar 6, 2026
mitxpro/tasks.py Outdated
log = logging.getLogger(__name__)


@app.task(acks_late=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be a shared task; it doesn't need to depend on an app.

Comment on lines +774 to +784
CRON_CLEAR_TOKENS_HOURS = get_string(
name="CRON_CLEAR_TOKENS_HOURS",
default=0,
description="'hours' value for the 'clear-expired-tokens' scheduled task (defaults to midnight)",
)
CRON_CLEAR_TOKENS_DAYS = get_string(
name="CRON_CLEAR_TOKENS_DAYS",
default="*",
description="'day_of_month' value for 'clear-expired-tokens' scheduled task (default will run every day)",
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more like a one-off setting, and I don't think we are going to tweak it often. We should probably remove these settings.

Comment on lines +914 to +923
"clear-expired-tokens": {
"task": "mitxpro.tasks.clear_expired_tokens",
"schedule": crontab(
minute=0,
hour=CRON_CLEAR_TOKENS_HOURS,
day_of_week="*",
day_of_month=CRON_CLEAR_TOKENS_DAYS,
month_of_year="*",
),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a decision on running it daily?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configurations can be shortened depending on the decision. Daily sounds too often for this task.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arslanashraf7 I’ve configured it similar to MITxOnline, so the task will run every Monday at 9 AM.

Comment on lines +556 to +559
"REFRESH_TOKEN_EXPIRE_SECONDS": {
"description": "Number of seconds until a refresh token expires",
"required": false
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you run generate_app_json command for this?

mitxpro/tasks.py Outdated
def clear_expired_tokens():
"""Clear expired OAuth2 access, refresh, and ID tokens via the cleartokens management command."""
log.info("Starting clear_expired_tokens...")
call_command("cleartokens")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In mitodl/mitxonline#3248, we switched from the command to the direct method from oauth2_provider.models import clear_expired. Perhaps we should do the same.

Comment on lines +23 to +65
def test_clear_tokens_does_not_delete_unexpired_tokens(settings):
"""Test that cleartokens does not delete tokens that have not yet expired"""
settings.OAUTH2_PROVIDER = {
**settings.OAUTH2_PROVIDER,
"REFRESH_TOKEN_EXPIRE_SECONDS": 60 * 60 * 24 * 30, # 30 days
}

user = UserFactory.create()
application = Application.objects.create(
name="test-cleartokens-app",
client_type="confidential",
authorization_grant_type="authorization-code",
skip_authorization=True,
)

now = timezone.now()

# Create an unexpired access token (expires 1 hour from now)
access_token = AccessToken.objects.create(
user=user,
application=application,
token=uuid.uuid4().hex,
expires=now + timedelta(hours=1),
scope="read write",
)

# Create a non-revoked refresh token
refresh_token = RefreshToken.objects.create(
user=user,
application=application,
token=uuid.uuid4().hex,
access_token=access_token,
)

tasks.clear_expired_tokens()

# Unexpired tokens should still exist
assert AccessToken.objects.filter(pk=access_token.pk).exists()
assert RefreshToken.objects.filter(pk=refresh_token.pk).exists()


@pytest.mark.django_db
def test_clear_tokens_deletes_expired_tokens(settings):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these could be converted into a single parameterized test. This will also reduce the code by a large extent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants