Skip to content

BB2-4781: Revoke prior tokens on new auth flow#1605

Draft
JamesDemeryNava wants to merge 10 commits into
masterfrom
jamesdemery/bb2-4781-revoke-prior-tokens-on-new-auth-flow
Draft

BB2-4781: Revoke prior tokens on new auth flow#1605
JamesDemeryNava wants to merge 10 commits into
masterfrom
jamesdemery/bb2-4781-revoke-prior-tokens-on-new-auth-flow

Conversation

@JamesDemeryNava
Copy link
Copy Markdown
Contributor

JIRA Ticket:
BB2-4781

What Does This PR Do?

This change ensures that if there are any previously existing access tokens or refresh tokens for an user_id/app_id pair after going through a new v3 auth flow, that there are specific DB updates that take place upon a new auth flow for the same user_id/app_id pair:

  • If the oauth2_provider_accesstoken record has an expires value that is greater than the current time, mark the expires
  • If there is a oauth2_provider_refreshtoken record (there won't be if there is a CAN flow), associated with one of those previously existing oauth2_provider_accesstoken records, mark the access_token_id column to null, and the revoked column to the current time so the token can no longer be used
  • No changes to how authorization_dataaccessgrant records are handled

What Should Reviewers Watch For?

If you're reviewing this PR, please check for these things in particular:

  • Are the unit tests sufficient? Are there other things that we should add tests for?
  • Should we revoke tokens regardless of the version? Ticket calls for only v3, but could see it being for all versions

Validation

  • Using Postman, go through a v2 auth flow for a BBUser, note which one you use and what the auth_user.id is for that user
    • Query the following tables for that user:
    • select * from authorization_dataaccessgrant where beneficiary_id = {{user_id}} order by id desc
    • select * from oauth2_provider_accesstoken where user_id = {{user_id}} order by id desc
    • select * from oauth2_provider_refreshtoken where user_id = {{user_id}} order by id desc
    • Go through another v2 auth flow for the same user in Postman.
    • Grab the first generated oauth2_provider_accesstoken.token value for that user.
    • In Postman, run a FHIR resource call. Under Authorization, for Auth Type, select Bearer Token, and then paste in the token value from the oauth2_provider_accesstoken record. See that the call will succeed
    • Then, go through a v3 auth flow in Postman for that same user, try to run the same FHIR resource call, and it will fail with an error of 'Authentication credentials were not provided.', status code will be a 401
    • Query the same three database tables again. You will see that the two prior oauth2_provider_refreshtoken will have a null access_token_id and a revoked value set to the current datetime. The two prior oauth2_provider_accesstoken will have an expires value set to the current datetime
  • Can repeat the same steps, but do all v3 auth flows. Should also refresh a token and make sure that is working as expected. Also can do an auth flow where you deselect the demographic scopes checkbox, as that deletes prior access and refresh token records.

What Security Implications Does This PR Have?

Please indicate if this PR does any of the following:

  • Adds any new software dependencies
  • Modifies any security controls
  • Adds new transmission or storage of data
  • Any other changes that could possibly affect security?
  • Yes, one or more of the above security implications apply. This PR must not be merged without the ISSO or team
    security engineer's approval.

Any Migrations?

  • Yes, there are migrations
    • The migrations should be run PRIOR to the code being deployed
    • The migrations should be run AFTER the code is deployed
    • There is a more complicated migration plan (downtime,
      etc)
  • No migrations

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thought there would need to be changes to this file, but in the ended it wound up just being using the HTTPStatus library rather than plain integer status codes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ya I feel like using the HTTPStatus library should be a standard going forward

@ryan-morosa ryan-morosa self-assigned this May 28, 2026
Copy link
Copy Markdown
Contributor

@jimmyfagan jimmyfagan left a comment

Choose a reason for hiding this comment

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

Left a few comments, but more generally, we might want to review the mural again to make sure we're on the right track regarding whether this should be for v2 or for v2 and v3, and regarding whether this should take effect with the submission of the permission screen, or with the POST token call. I would think this should probably be for v2 and v3, and I would also think that it should happen on the permission screen rather than the post, but we could probably check to make sure that covers all of the cases we talked through previously.

Comment thread apps/dot_ext/utils.py

for access_token in prior_access_tokens:
try:
refresh_token = get_refresh_token_model().objects.get(access_token=access_token.id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(nit) We already get the RefreshToken model above and should be able to reuse it here.

Comment thread apps/dot_ext/utils.py
Comment on lines +373 to +374
access_token.expires = timezone.now()
access_token.save()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we want to match the above and still only do this if expires > now? Might be able to set the access_token.expires before trying to get the refresh token to avoid having to set it in two spots as well.

Comment thread apps/dot_ext/utils.py
return all(ord(char) <= 383 for char in text) and bool(text)


def revoke_prior_tokens_for_user_and_app_if_they_exist(user_id: int, app_id: int) -> None:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A more general comment, our method of doing this elsewhere is by using remove_application_user_pair_tokens_data_access. Is there much materially different about the two functions that justifies keeping both, or could we reuse the existing function and get rid of this altogether? They also seem to be utilized in different stages of the process, but getting closer to the existing behavior of remove_application_user_pair_tokens_data_access might have a little more precedent (I think it's during the permission screen processing rather than on POST token, since that's when the user's new preferences should take effect).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It looks like remove_application_user_pair_tokens_data_access deletes the data_access_grant as well as the token records. The function I added just renders the prior tokens unusable, but does not delete them. The benefit there is we can see how many tokens a beneficiary may have had, though i'm sure how big of a benefit that really is. We certainly can use remove_application_user_pair_tokens_data_access instead if we prefer to delete the prior tokens and access grant, and then create a new access grant as well on subsequent authorizations.

@JamesDemeryNava
Copy link
Copy Markdown
Contributor Author

Left a few comments, but more generally, we might want to review the mural again to make sure we're on the right track regarding whether this should be for v2 or for v2 and v3, and regarding whether this should take effect with the submission of the permission screen, or with the POST token call. I would think this should probably be for v2 and v3, and I would also think that it should happen on the permission screen rather than the post, but we could probably check to make sure that covers all of the cases we talked through previously.

I initially implemented it for all versions, and at the end added a check for just v3. The reason for that was AC #2 and 3 on the ticket. Would be easy to do it for all versions, the mural (section TOKENS 4/2026), does not mention anything about specific versions, so I will change it back to be for all versions?

Screenshot 2026-05-28 at 11 22 23 AM

The mural also mentions that we should only delete old tokens if scopes are removed in a subsequent auth, but that is not in the ticket. Will raise these as an 11th at sync today. Shouldn't be difficult to remove prior tokens when they get to the permissions screen rather than after clicking connect if that's what we want.

@JamesDemeryNava JamesDemeryNava marked this pull request as draft May 28, 2026 19:35
…w from post of TokenView. Revoke all previous tokens as the newest one will not have been created yet at that point
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants