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 6 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
@@ -37,6 +37,14 @@ def is_refresh_token_expired?
refresh_token_expires_at < Time.current
end

def revoke_api_token
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice.

users = User.all

users.each do |user|
ApiCredential.create!(user: user, api_token_digest: Digest::SHA256.hexdigest(SecureRandom.hex(18)), refresh_token_digest: Digest::SHA256.hexdigest(SecureRandom.hex(18)))
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.

Would this make hashed values of refresh_token and refresh_token_digest different? Since your using a completely new SecureRandom.hex(18). If so, this should not be the case.

It should hash the current refresh_token.

@@ -100,4 +100,22 @@
expect(api_credential.refresh_token_digest).to eq(Digest::SHA256.hexdigest(refresh_token))
end
end

describe "#revoke_api_token" do
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

@@ -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
  ...

@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.

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