security
Verification Service
Is this code still valid?
Send verification and password-reset codes that expire after 15 minutes.
Verification codes, remembered devices, rate-limit counters and a security audit log share one table. Each kind of item expires on its own schedule: codes after 15 minutes, device tokens after 30 days and audit entries after 90 days. An index by user finds a user's earlier codes, so the app can invalidate them before sending a new one.
The model
VerificationCode
A time-limited code for email/phone verification or password reset
- pk
- CODE#<code>
- sk
- CODE#<code>
Attributes: PK (S), SK (S), GSI1PK (S), GSI1SK (S), code (S), userId (S), codeType (S), deliveryMethod (S), deliveryTarget (S), isUsed (BOOL), createdAt (S), ttl (N)
DeviceToken
A registered device for push notifications or remember-me functionality
- pk
- DEVICE#<deviceId>
- sk
- TOKEN#<tokenId>
Attributes: PK (S), SK (S), GSI1PK (S), GSI1SK (S), deviceId (S), tokenId (S), userId (S), deviceType (S), deviceName (S), pushToken (S), lastActiveAt (S), ipAddress (S), userAgent (S), createdAt (S), ttl (N)
AuditLog
Security event log for compliance and forensics
- pk
- USER#<userId>
- sk
- AUDIT#<timestamp>
Attributes: PK (S), SK (S), userId (S), eventType (S), eventDetails (M), ipAddress (S), userAgent (S), deviceId (S), success (BOOL), failureReason (S), timestamp (S), ttl (N)
RateLimitCounter
Track rate limits for code requests per user
- pk
- RATELIMIT#<userId>#<action>
- sk
- WINDOW#<windowStart>
Attributes: PK (S), SK (S), userId (S), action (S), windowStart (S), count (N), ttl (N)
Access patterns
- GetItemValidate verification code
Check if a code is valid (exists, not expired, not used)
- Query · GSI1Invalidate previous codes
Query and delete user's existing codes of the same type before creating new one
- TransactWriteItemsRegister device token
Create or update a device token for push notifications
- Query · GSI1Revoke all user's tokens
Delete all device tokens for a user (e.g., on password change)
- QueryQuery user audit history
Get security events for a user within a time range
- QueryCheck rate limit
Check if user has exceeded rate limit for an action
Design notes
TTL handles all expirationwhy
No background jobs needed to clean up expired codes or tokens. DynamoDB TTL automatically deletes items after expiration. AWS documents deletion as typically within a few days of the ttl passing, with no guarantee attached, so always check the ttl attribute when validating.
Code lookup vs user lookupwhy
VerificationCode has dual access: by code (PK) for validation, by user (GSI1) for invalidation. Same item, different access paths. This avoids needing to know the user ID when validating.
Audit log TTL for compliancetrade-off
90-day retention matches many compliance requirements. Adjust based on your needs. For permanent audit logs, omit the TTL attribute or set it far in the future.
Rate limit with atomic counterwhy
RateLimitCounter uses ADD for atomic increment. If count exceeds threshold, reject the request. The TTL ensures old windows are cleaned up automatically.
Alternative: Redis for ephemeral dataalternative
For very high-volume verification codes (millions/day), consider Redis with TTL. DynamoDB is cost-effective for moderate volume but Redis may be more economical at extreme scale.
Registering a device is a transaction the page cannot runtrade-off
Register device token is a TransactWriteItems: it puts the device token and appends the device_added entry to the user's audit log, so a device is never registered without a record of it. The engine in this page does not support TransactWriteItems, so it is not offered as a run.
Taught in the course
- The key is the address - GetItem needs the whole primary key: partition and sort.
- Model a one-to-many with one partition - A parent and its children share a partition key, so one Query fetches them together.
Check if a code is valid (exists, not expired, not used)
Choose an access pattern above, or build your own request. See what comes back and what it costs.
Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.