How RomM Handles Token Refresh for API Authentication: Secure OAuth Implementation
RomM implements a secure, single-use refresh token flow using Redis-backed JWT storage to prevent replay attacks and enforce automatic token rotation.
RomM, the open-source ROM management system, secures its API endpoints using a custom OAuthHandler that manages token lifecycle through Redis-backed storage. When handling token refresh for API authentication, the application employs a strict rotation mechanism that guarantees each refresh token can only be used once, eliminating the risk of credential replay while maintaining seamless user sessions.
Token Creation and Redis Storage
Generating the Refresh Token
When a client authenticates with a password, RomM’s OAuthHandler creates both a short-lived access token and a long-lived refresh token. In backend/handler/auth/base_handler.py, the create_refresh_token() method (lines 82-98) handles this process:
- It copies the authentication payload and adds a unique JWT ID (
jti) Claim - It sets the
typeclaim to"refresh"to distinguish it from access tokens - It signs the JWT using the symmetric key
ROMM_AUTH_SECRET_KEYwith a configurableexpires_delta
This implementation ensures that every refresh token carries a unique identifier that can be tracked and invalidated independently.
Single-Use Storage Mechanism
Immediately after creation, RomM stores the token’s metadata in Redis to enforce single-use semantics. At lines 98-102 of the base handler, the system executes:
- Storage of the key
refresh-jti:<jti>with the valueb"valid" - TTL (time-to-live) set equal to the token’s lifetime
- Automatic expiration when the TTL elapses
This Redis entry acts as a "ticket" that must be present for the token to be valid. Once consumed, the key is permanently removed, preventing any possibility of reuse.
Token Validation and Consumption
The Consumption Process
When a client sends a request with grant_type=refresh_token to the /api/auth endpoint, RomM invokes OAuthHandler.consume_refresh_token() (lines 105-138 in backend/handler/auth/base_handler.py). This method performs rigorous validation:
- Signature verification – Validates the JWT signature using
ROMM_AUTH_SECRET_KEY - Expiry check – Compares the current UTC time against the
expclaim; expired tokens raiseOAuthCredentialsException - Issuer validation – Confirms the
issclaim equals"romm:oauth" - Type confirmation – Ensures the
typeclaim is"refresh"
Atomic Deletion and User Verification
The critical security step occurs via Redis atomic operations. The handler uses redis_client.getdel() to simultaneously retrieve and delete the refresh-jti:<jti> key:
- If the key is missing or not equal to
b"valid", the token is rejected immediately - If valid, the
subclaim extracts the username for database lookup viadb_user_handler.get_user_by_username() - The user must exist and be enabled; otherwise, authentication fails
This atomic getdel() operation guarantees that even concurrent requests with the same token cannot succeed twice, effectively implementing token rotation at the infrastructure level.
API Endpoint Integration
The /api/auth endpoint in backend/endpoints/auth.py orchestrates the refresh workflow. At lines 130-138, it inspects form_data.grant_type and routes refresh requests to the handler:
# From backend/endpoints/auth.py
if form_data.grant_type == "refresh_token":
user, claims = await oauth_handler.consume_refresh_token(
form_data.refresh_token
)
Upon successful consumption (lines 157-168), the endpoint immediately issues:
- A new access token with a short expiration (e.g., 5 minutes)
- A fresh refresh token with a new
jtiand updated expiration
This rotation ensures that clients always receive a new credential pair, while the old refresh token becomes permanently invalid.
Code Example: Implementing the Refresh Flow
The following examples demonstrate the core implementation patterns found in RomM’s source code:
# 1️⃣ Issue a refresh token (after successful password authentication)
payload = {
"sub": user.username, # User identifier
"iss": "romm:oauth", # Issuer flag
}
refresh_token = oauth_handler.create_refresh_token(
data=payload,
expires_delta=timedelta(minutes=30),
)
# 2️⃣ Consume a refresh token (inside the /api/auth endpoint)
# Client sends: grant_type=refresh_token, refresh_token=<token>
user, claims = await oauth_handler.consume_refresh_token(refresh_token)
# 3️⃣ Issue new token pair after successful consumption
access_token = oauth_handler.create_access_token(
data={"sub": user.username, "iss": "romm:oauth"},
expires_delta=timedelta(minutes=5),
)
new_refresh_token = oauth_handler.create_refresh_token(
data={"sub": user.username, "iss": "romm:oauth"},
expires_delta=timedelta(minutes=30),
)
Summary
RomM’s approach to token refresh for API authentication combines cryptographic validation with database-backed single-use enforcement:
- Unique token identification via the
jticlaim stored in Redis underrefresh-jti:<jti> - Atomic consumption using
redis_client.getdel()to prevent race conditions and replay attacks - Automatic rotation that invalidates the old token while generating a new one with each refresh cycle
- Strict validation of issuer, type, expiration, and user status before issuing new credentials
Frequently Asked Questions
How does RomM prevent refresh token replay attacks?
RomM stores each refresh token’s jti (JWT ID) in Redis with the key pattern refresh-jti:<jti>. When consuming a token, the system uses redis_client.getdel() to atomically retrieve and delete this key. If the key is already gone, the token is rejected, ensuring that even if an attacker intercepts the token, they cannot use it after the legitimate client has refreshed.
What happens to the old refresh token after a successful refresh?
The old refresh token becomes permanently invalid immediately upon use. The consume_refresh_token() method deletes the corresponding Redis entry during validation, and the /api/auth endpoint issues a brand new refresh token with a fresh jti and expiration time. This rotation mechanism ensures no lingering valid tokens exist in the system.
Where does RomM store the refresh token metadata?
RomM stores refresh token metadata in Redis, not in the primary database. Specifically, it creates keys with the format refresh-jti:<jti> where <jti> is the unique JWT ID embedded in the token. These keys have a TTL matching the token’s expiration and hold the value b"valid" to indicate active status.
How long do refresh tokens remain valid in RomM?
The refresh token lifetime is configurable via the expires_delta parameter passed to create_refresh_token(). While the source code examples show 30-minute durations, administrators can adjust this value during token creation. Regardless of the duration, tokens automatically expire when their TTL elapses in Redis or when they are consumed, whichever comes first.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →