Fix OAuth token expiry and implement refresh token rotation#3102
Conversation
… token doesn't lead to internal server error
Greptile SummaryThis PR fixes two long-standing OAuth issues: access tokens were never checked against their expiry date, and refresh tokens could be reused indefinitely. It adds expiry validation to Key observations:
Confidence Score: 2/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Client sends refresh request] --> B[consume_refresh_token called]
B --> C{JWT decode OK?}
C -- No --> D[raise OAuthCredentialsException 401]
C -- Yes --> E{Token expired?}
E -- Yes --> D
E -- No --> F{iss == romm:oauth AND type == refresh?}
F -- No --> D
F -- Yes --> G[Redis GETDEL on jti]
G --> H{Returned valid?}
H -- No --> D
H -- Yes --> I[Lookup user in DB]
I --> J{User exists and enabled?}
J -- No --> D
J -- Yes --> K[Issue new access token]
K --> L[Issue new refresh token and store jti in Redis]
L --> M[Return both tokens to client]
N[Client sends API request with stale bearer token] --> O[HybridAuthBackend.authenticate]
O --> P[get_current_active_user_from_bearer_token called]
P --> Q{Token expired?}
Q -- No --> R[Continue auth flow normally]
Q -- Yes --> S[raises OAuthCredentialsException]
S --> T[No try/except in hybrid_auth.py]
T --> U[Exception propagates through AuthenticationMiddleware]
U --> V[401 returned even on public routes]
|
|
@HydroSulphide i'll have a look once the comments and failing tests are fixed! |
Three tests were also implemented to check initial implementation that now invalidates expired access and refresh tokens and also rotating refresh tokens. Since I introduced wrapper functions for create_oauth_token to distinguish between access and refresh token there is no need to set the token type in the data dict, since the type is now enforced in the wrapper functions create_access_token and create_refresh_token. By convention I renamed create_oauth_token to _create_oauth_token as it is considered a private helper function now.
a456e18 to
0233697
Compare
Description
The current state of OAuth token implementation is that they are always valid, because of a missing check against their expiration date. I implemented this check in
get_current_active_user_from_bearer_token(). I also implemented refresh token rotation.This PR changes the OAuth token flow so expired access tokens are rejected correctly, refresh tokens are consumed and rotated on refresh, and old refresh tokens can no longer be reused. The
/tokenendpoint now usesconsume_refresh_token()for the refresh flow, creates a new refresh token on every successful refresh, and returnsrefresh_token,expires_in, andrefresh_expires_inin the response. I also updated theTokenResponsetype accordingly.Additionally, I added a follow-up fix for bearer authentication in
hybrid_auth.py: ifget_current_active_user_from_bearer_token()raises anHTTPExceptionwith status code401, it is now handled gracefully instead of causing an internal server error during API calls with expired access tokens.I tested the changes locally with a token rotation test flow that verifies valid access tokens work before expiry, expired access tokens are rejected by the server, refresh tokens produce a new token pair, old refresh tokens fail after rotation, and expired refresh tokens are rejected.
Most importantly: I fixed a typo in a comment.
Checklist