Refresh Token
Data Entity
Description
Rotating refresh tokens issued by the Authentication Module to maintain persistent sessions for users across both the Meander Mobile App and Admin Web Portal. Each token belongs to a rotation family; consuming a token invalidates it and issues a replacement. Reuse of a revoked token triggers full family revocation to mitigate token theft.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key, unique per issued token | PKrequiredunique |
user_id |
uuid |
Foreign key to the user who owns this token | required |
session_id |
uuid |
Foreign key to the session this token belongs to | required |
token_hash |
string |
SHA-256 hash of the raw refresh token. Never stored in plaintext. Used for lookup on token presentation. | requiredunique |
family_id |
uuid |
Groups all tokens in a single rotation chain. If any revoked token in a family is reused, the entire family is immediately revoked (token theft detection). | required |
rotation_count |
integer |
Number of times this specific family has been rotated. Monotonically increasing; used to enforce expected rotation sequence. | required |
is_revoked |
boolean |
Whether this token has been invalidated. Set true on rotation (token consumed), logout, admin revocation, or security event. | required |
revoked_at |
datetime |
UTC timestamp when token was revoked. Null if still active. | - |
revoked_reason |
enum |
Reason the token was revoked. Used for audit and security analysis. | - |
expires_at |
datetime |
UTC timestamp when this token expires regardless of revocation state. Tokens past this timestamp are unconditionally rejected. | required |
issued_at |
datetime |
UTC timestamp when the token was issued. | required |
ip_address |
string |
Client IP address at token issuance. Stored for audit and anomaly detection. IPv4 or IPv6. | - |
user_agent |
string |
HTTP User-Agent header at issuance. Used for session display in Session Management UI and for anomaly detection. | - |
device_fingerprint |
string |
Optional opaque device identifier from the mobile client (platform + model hash). Enables per-device session revocation from the Session Management UI. | - |
created_at |
datetime |
Record creation timestamp (same as issued_at; kept for consistency with ORM conventions). | required |
updated_at |
datetime |
Last modification timestamp. Updated on revocation. | required |
Database Indexes
idx_refresh_tokens_token_hash
Columns: token_hash
idx_refresh_tokens_user_id
Columns: user_id
idx_refresh_tokens_session_id
Columns: session_id
idx_refresh_tokens_family_id
Columns: family_id
idx_refresh_tokens_user_active
Columns: user_id, is_revoked
idx_refresh_tokens_expires_at
Columns: expires_at
Validation Rules
token_hash_format
error
Validation failed
expires_at_future
error
Validation failed
user_id_references_active_user
error
Validation failed
session_id_references_active_session
error
Validation failed
revoked_fields_consistency
error
Validation failed
rotation_count_monotonic
error
Validation failed
ip_address_format
warning
Validation failed
Business Rules
rotating_token_on_use
When a valid refresh token is presented, it must be immediately revoked (revoked_reason='rotation') and a new token issued in the same family with rotation_count incremented. The old token becomes unusable the instant the new one is issued.
reuse_detection_family_revocation
If a token that is already revoked (is_revoked=true) is presented, all tokens in the same family_id are immediately revoked with revoked_reason='security_event'. This assumes token theft — an attacker replayed a stolen token after the legitimate client already rotated it.
expiry_hard_reject
Tokens past expires_at are rejected unconditionally, even if is_revoked=false. No grace period.
admin_revocation_cascades_to_family
When an admin revokes a session from Session Management UI, all refresh tokens belonging to that session_id are marked is_revoked=true with revoked_reason='admin_revoke'. This ensures the user is fully signed out across all clients holding that session's tokens.
user_logout_revokes_token
On explicit logout, the current refresh token is revoked (revoked_reason='logout'). The entire family may also be revoked if the logout is a 'sign out all devices' operation.
token_plaintext_never_stored
The raw token value must never be persisted. Only the SHA-256 hash is stored in token_hash. The raw value is returned to the client once at issuance and never retrievable from the database.
mobile_secure_store_only
The Flutter client must store the raw refresh token exclusively in the platform secure store (Keychain on iOS, Keystore on Android) via secure-token-store. It must never be written to Drift/SQLCipher local DB, shared preferences, or any other non-secure storage.
expired_token_cleanup
Tokens past expires_at AND older than 30 days may be hard-deleted by a scheduled cleanup job. Active and recently-revoked tokens within the 30-day window are retained for audit trail.