Skip to content

feat(api-tokens): API token expiry and revoke-by-key endpoint#14932

Open
svader0 wants to merge 9 commits into
DefectDojo:devfrom
svader0:token-revocation
Open

feat(api-tokens): API token expiry and revoke-by-key endpoint#14932
svader0 wants to merge 9 commits into
DefectDojo:devfrom
svader0:token-revocation

Conversation

@svader0
Copy link
Copy Markdown
Contributor

@svader0 svader0 commented May 29, 2026

This PR addresses issue/feature request #14890.

EDIT: The new endpoints have changed significantly from the following description. See thread below.

Description

Currently, DefectDojo has no mechanism for managing API tokens via the API. In case a token gets leaked or needs to be revoked, the only existing solution is to call reset_api_token, which simply rotates the token. Superusers also have no way to see what tokens exist, who owns them, or when they were issued. This PR is an attempt to close the gap a little. An addition /api/v2/api-tokens/ exposes the ability to list, retrieve, and revoke tokens. Superusers and Global Owners get instance-wide visibility of token metadata, whereas standard users have only self-service to revoke or view their own credentials. Tokens themselves are never returned by any of these new endpoints, so that a breach doesn't become a total disaster.

In addition to revocation, this PR introduces a per-user token expiry. A superuser can set a token_expiry on a specific user via the existing user_contact_infos endpoint. Expired tokens are rejected at authentication time. In addition to this, a new instance-wide environment variable DD_API_TOKEN_DEFAULT_EXPIRY_DAYS (talk about a mouthful...) applies a default lifetime to all generated tokens. The default is 0, a.k.a. no expiry, so there should be no issues with existing deployments unless they choose to opt into the change.

Detailed Changes

New endpoints under /api/v2/api-tokens/:

  • GET /api/v2/api-tokens/ --> list active tokens. Superusers and Global Owners see all users; regular users see only their own. Filter with ?user_id=<id>.
  • GET /api/v2/api-tokens/{user_id}/ --> retrieve token metadata. The token key is never exposed. Non-superusers requesting another user's token receive 404.
  • DELETE /api/v2/api-tokens/{user_id}/ --> immediately revoke a token. Superusers can revoke any token; regular users can only revoke their own. Returns 204.

Token expiry:

  • New token_expiry field on UserContactInfo.
  • Superusers can override expiry per-user via the existing PATCH /api/v2/user_contact_infos/{id}/ endpoint.
  • Requests with an expired token receive a 403: expired token.
  • New env var DD_API_TOKEN_DEFAULT_EXPIRY_DAYS (default 0 = no expiry).
    • When > 0, every token rotation sets token_expiry to now + N days.
  • token_expiry is now exposed on GET /api/v2/users/{id}/.

UI:

  • Both the classic and the new UI templates shows either the current expiry datetime or "does not expire".

Other implementation stuff:

  • New ExpiringTokenAuthentication replaces the stock class in DEFAULT_AUTHENTICATION_CLASSES. This is so that our expiry check runs on each token.
  • New ApiTokenViewSet with lookup_field = "user_id"

Test results

10 new unit tests added in unittests/test_apiv2_token.py:

  • List as superuser sees all tokens. Also verifies that the key field is never exposed.
  • List as non-superuser sees only own token.
  • Revoke as superuser deletes the token.
  • Self-revoke deletes own token.
  • Revoke clears token_expiry.
  • Expired token rejected with correct status and message.
  • UserSerializer exposes token_expiry.
  • DD_API_TOKEN_DEFAULT_EXPIRY_DAYS=7 snaps expiry on reset.
  • DD_API_TOKEN_DEFAULT_EXPIRY_DAYS=0 leaves expiry null.

All 10 tests pass. The UI edits I checked manually, and they do indeed appear.

Documentation

docs/content/automation/api/api-v2-docs.md updated with new endpoints, expiry behaviour, and the DD_API_TOKEN_DEFAULT_EXPIRY_DAYS variable.

Checklist

This checklist is for your information.

  • Make sure to rebase your PR against the very latest dev.
  • Features/Changes should be submitted against the dev.
  • Bugfixes should be submitted against the bugfix branch.
  • Give a meaningful name to your PR, as it may end up being used in the release notes.
  • Your code is Ruff compliant (see ruff.toml).
  • Your code is python 3.13 compliant.
  • If this is a new feature and not a bug fix, you've included the proper documentation in the docs at https://github.com/DefectDojo/django-DefectDojo/tree/dev/docs as part of this PR.
  • Model changes must include the necessary migrations in the dojo/db_migrations folder.
  • Add applicable tests to the unit tests.
  • Add the proper label to categorize your PR.

svader0 added 7 commits May 26, 2026 12:13
- Add token_expiry DateTimeField to UserContactInfo (was missing from
  initial commit despite migration referencing it)
- Register ApiTokenViewSet at api-tokens/ in urls.py (was missing from
  initial commit despite ViewSet existing in views.py)
- Add unit tests for list, retrieve, revoke, expiry enforcement, and
  default-expiry-on-reset behaviours
@svader0 svader0 requested review from Maffooch and mtesauro as code owners May 29, 2026 23:59
@github-actions github-actions Bot added New Migration Adding a new migration file. Take care when merging. settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR apiv2 docs unittests ui labels May 30, 2026
@dryrunsecurity
Copy link
Copy Markdown

dryrunsecurity Bot commented May 30, 2026

DryRun Security

This pull request triggers multiple critical findings from the configured codepaths analyzer, indicating sensitive edits across core files such as models.py, urls.py, authentication.py, views.py, and serializers.py, which may bypass intended security controls; additionally, a medium-severity issue reveals that API token auto-creation in user/views.py bypasses token expiry policies by failing to initialize the token_expiry field, potentially allowing indefinite token validity.

🔴 Configured Codepaths Edit in dojo/api_v2/serializers.py (drs_0220be96)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/api_v2/views.py (drs_e0bcfe7f)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/db_migrations/0269_usercontactinfo_token_expiry.py (drs_850baef5)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/models.py (drs_0aa4b6d6)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/templates/dojo/api_v2_key.html (drs_0d836839)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/urls.py (drs_d1116fbb)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/user/authentication.py (drs_bbd8f2de)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/user/views.py (drs_f521fc46)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/models.py (drs_b492fa69)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/urls.py (drs_76d499ca)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/user/authentication.py (drs_a6fb3017)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/user/authentication.py (drs_637a190b)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/api_v2/views.py (drs_f690e06f)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/user/authentication.py (drs_b4eef600)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/models.py (drs_0b272573)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/urls.py (drs_8620e6e2)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/api_v2/serializers.py (drs_0ed544d0)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/api_v2/views.py (drs_32b9ac12)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🔴 Configured Codepaths Edit in dojo/urls.py (drs_2a60837c)
Vulnerability Configured Codepaths Edit
Description Sensitive edits detected for this file. Sensitive file paths and allowed authors can be configured in .dryrunsecurity.yaml.
🟡 Token auto-creation bypasses expiry policy in dojo/user/views.py (drs_34ed9c3e)
Vulnerability Token auto-creation bypasses expiry policy
Description In dojo/user/views.py, the api_v2_key view automatically creates an API token for users who do not have one. This creation process does not initialize the token_expiry field in the user's UserContactInfo. As a result, newly created tokens via this path have token_expiry set to None (or effectively uninitialized). The ExpiringTokenAuthentication middleware in dojo/user/authentication.py only enforces expiration if uci.token_expiry is set and in the past; if it is None, the token remains valid indefinitely, allowing users to effectively bypass any configured expiration policy.

except Token.DoesNotExist:
api_key = Token.objects.create(user=request.user)

We've notified @mtesauro.


Comment to provide feedback on these findings.

Report false positive: @dryrunsecurity fp [FINDING ID] [FEEDBACK]
Report low-impact: @dryrunsecurity nit [FINDING ID] [FEEDBACK]

Example: @dryrunsecurity fp drs_90eda195 This code is not user-facing

All finding details can be found in the DryRun Security Dashboard.

@valentijnscholten
Copy link
Copy Markdown
Member

Thank you for the PR. At first glance there seem to be various functionalities in this PR. I can imagine having an expiry datetime/period for tokens is good to have and not to hard to maintain. But I'm unsure of the other items like allowing super users to manage other users tokens etc. Do you have good example of use cases for each feature introduced by the PR?

@svader0
Copy link
Copy Markdown
Contributor Author

svader0 commented May 31, 2026

Thanks for taking a look! I definitely agree about the expiry date/time period. After thinking about it for a while, it seems that the practical use-case of the token-management stuff was a little too overstated, so I made a few changes to slim it down.

Token expiry: Like you said, it's fairly easy implementation and maintenance, and having an expiration date on API keys is pretty standard behavior. If a key were to get leaked, having an expiration date will reduce risk. I think this should stay as-is.

Token Management: The thinking was that if a token was compromised, as a superuser you may want to revoke it immediately without issuing a replacement. I was originally following the train of logic set about by the issue I was responding to, where you'd want a superuser to be able to have a list of existing tokens and then be able to revoke them selectively. But I think that in trying to solve that problem, I accidentally made things a little convoluted here, so I went ahead and simplified the solution.

Now there is one new endpoint in this PR, POST /api/v2/api-tokens/revoke/, where you supply a key via JSON payload, and it gets revoked. Currently, you can only rotate a user's key by user lookup, you can't eradicate a specific key itself. With this implementation, we avoid the API bloat of needing to look up all existing tokens, filter, and revoke like I had previously.
This would solve the use case where, "uh oh!", a token gets leaked, and I want to eradicate it immediately and programmatically without tracking down its owner and rotating it.

If you think these two additions are too distinct to be in one PR, I am happy to open up a second and split them up.

@svader0 svader0 changed the title feat(api-tokens): list, revoke, and per-user expiry feat(api-tokens): API token expiry and revoke-by-key endpoint May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

apiv2 docs New Migration Adding a new migration file. Take care when merging. settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR ui unittests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants