Skip to content
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

Feature/6222 - Endpoint /api/v1/users/sign_out revokes access token and refresh token on request #6241

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

xihai01
Copy link
Collaborator

@xihai01 xihai01 commented Feb 24, 2025

What github issue is this PR for, if any?

Resolves #6222

What changed, and why?

Added /api/v1/users/sign_out endpoint so both the access and refresh tokens for that user is cleared from the api_credentials table and set to nil.

  • Added request and model specs for the sign out route
  • Generated and updated swagger file
  • Added helper function to api credential model to clear tokens
  • Added sign out to routes and session controller
  • Added seed data for populating tokens in api credential table in dev environment

Why?: for added security - tokens should be removed from api_credentials table because user is no longer signed in.

How is this tested? (please write tests!) 💖💪

Token Destroyer Helper Function tests (2 in total) → spec/models/api_credential_spec.rb
Sign Out Request Test for 200 and 401 Response Cases → spec/requests/api/v1/users/sessions_spec.rb

Screenshots please :)

Testing sign out with postman on localhost

Steps:
First we sign in to fetch the refresh token
Lastly we sign out and pass in refresh token in the request authorization header
Screenshot 2025-02-26 at 12 30 58 PM

Feelings gif (optional)

very strange

@xihai01 xihai01 added the codethechange for codethechange developers label Feb 24, 2025
@xihai01 xihai01 requested a review from 7riumph February 24, 2025 20:47
@xihai01 xihai01 self-assigned this Feb 24, 2025
@github-actions github-actions bot added ruby Pull requests that update Ruby code Tests! 🎉💖👏 labels Feb 24, 2025
@compwron
Copy link
Collaborator

👀

let(:casa_org) { create(:casa_org) }
let(:volunteer) { create(:volunteer, casa_org: casa_org) }
let(:api_credential) { create(:api_credential, user: volunteer) }
let(:refresh_token) { api_credential.return_new_refresh_token![:refresh_token] }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Er, is this regenerating a refresh token a 2nd time?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think when you create the api credential table for the first time, the token digest fields are nil - at least in dev environment and so that is why I created the seed file to populate them.

I also need the plain text refresh token to be passed in the authorization header for the sign out.

Copy link
Collaborator

@7riumph 7riumph left a comment

Choose a reason for hiding this comment

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

Looks good so far, as mentioned on the issues acceptance criteria and in our discussions. Still need to...

  1. Add the sign_in route to routes.rb ( session#destroy )
  2. Add a destroy function to the sessions_controller.rb using the revocation function(s) helper you made
  3. Address comments, test route with whatever you'd like to use though my preference is curl, then should lgtm 😎

@xihai01 xihai01 marked this pull request as ready for review February 27, 2025 16:15
@@ -8,6 +8,22 @@ def create
end
end

def destroy
# fetch refresh token from request header
refresh_token = request.headers["Authorization"]&.split(" ")&.last
Copy link
Collaborator

@7riumph 7riumph Mar 1, 2025

Choose a reason for hiding this comment

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

Consider how we are structuring the response from the iOS app. If we're using the header, it's the access token or "api_token" that would go there, while the refresh token is in the body.

You'll need both in order to check whose tokens to revoke or set to nil on sign_out.

# fetch refresh token from request header
refresh_token = request.headers["Authorization"]&.split(" ")&.last
# find user's api credentials by refresh token
api_credential = ApiCredential.find_by(refresh_token_digest: Digest::SHA256.hexdigest(refresh_token))
Copy link
Collaborator

@7riumph 7riumph Mar 1, 2025

Choose a reason for hiding this comment

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

api_credential = ApiCredential searches user based on only one token, instead do.

 user_credentials = ApiCredential.find_by(
    api_token_digest: Digest::SHA256.hexdigest(access_token),
    refresh_token_digest: Digest::SHA256.hexdigest(refresh_token)
  )
  
if user_credentials
  user_credentials.revoke_api_token
  user_credentials.revoke_refresh_token
  render json: {message: "Signed out successfully."}, status: 200
else
  ...

Copy link
Collaborator Author

@xihai01 xihai01 Mar 4, 2025

Choose a reason for hiding this comment

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

I'm not sure if we should use the api_token for checking because it expires every 7 hours. Using the refresh token is more reliable because it has a longer lifespan. If we want an additional check, we can pass along the user id in the request body as well.
So we would have something like .find_by(user_id, refresh_token_digest). Let me know what you think.

Copy link
Collaborator

@7riumph 7riumph Mar 4, 2025

Choose a reason for hiding this comment

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

I see, traditionally though, it's the access token/our api_token that goes in the header on every response. Having it expire frequently is the point.

This goes beyond just sign-out and will reflect the shape of our responses from the client past sign-in for every request so should scope this.

But if we're going to sign the user out based on just one token value it should be the access token because that value is valid for less time/more secure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah I see. That makes sense now.
Then let's put the access token in the header and use that for sign out.

@7riumph
Copy link
Collaborator

7riumph commented Mar 1, 2025

@xihai01 Additionally, can you make edits to the app's sign out button itself, especially utilizing the dual token additions here

Implementing it should remove any confusion.

@xihai01
Copy link
Collaborator Author

xihai01 commented Mar 4, 2025

@xihai01 Additionally, can you make edits to the app's sign out button itself, especially utilizing the dual token additions here

Implementing it should remove any confusion.

Do we have a separate issue for the sign out button?

@7riumph
Copy link
Collaborator

7riumph commented Mar 4, 2025

@xihai01 Additionally, can you make edits to the app's sign out button itself, especially utilizing the dual token additions here
Implementing it should remove any confusion.

Do we have a separate issue for the sign out button?

No but can defiantly make one. This is the current AccountScreen with a Sign-out button.

Current behaviour is just a console log onPress={console.log('For Sign out')} would make a function using the auth base.

@xihai01
Copy link
Collaborator Author

xihai01 commented Mar 4, 2025

@xihai01 Additionally, can you make edits to the app's sign out button itself, especially utilizing the dual token additions here
Implementing it should remove any confusion.

Do we have a separate issue for the sign out button?

No but can defiantly make one. This is the current AccountScreen with a Sign-out button.

Current behaviour is just a console log onPress={console.log('For Sign out')} would make a function using the auth base.

ok I got it. I can work on this issue. Let me know if you havn't already and I will create an issue for it.

@7riumph
Copy link
Collaborator

7riumph commented Mar 4, 2025

@xihai01 Additionally, can you make edits to the app's sign out button itself, especially utilizing the dual token additions here
Implementing it should remove any confusion.

Do we have a separate issue for the sign out button?

No but can defiantly make one. This is the current AccountScreen with a Sign-out button.
Current behaviour is just a console log onPress={console.log('For Sign out')} would make a function using the auth base.

ok I got it. I can work on this issue. Let me know if you havn't already and I will create an issue for it.

Thanks, all good, here's the issue for end-to-end implementation.

@xihai01 xihai01 requested a review from 7riumph March 5, 2025 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
codethechange for codethechange developers ruby Pull requests that update Ruby code Tests! 🎉💖👏
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Endpoint /api/v1/users/sign_out revokes access token and refresh token on request
3 participants