Funky Tokens
Recently, I had the opportunity to examine the internals of a production authentication system built on Auth0 and OAuth2. As someone that learned quite a bit about security systems throughout this, I thought it might make sense to explain how an example production system works.
This exploration provided some insights into how modern web applications handle user registration, email verification, and secure token management.
The Architecture of Modern Authentication
Session Management and State Preservation
Modern web applications rely heavily on session management to maintain user state across multiple requests. Typically, these are stored as session headers in api requests. The system I examined used standard HTTP session cookies combined with OAuth2's state parameter for CSRF protection:
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
})Browser-like session management
The state parameter serves as a critical security mechanism, threading through the entire authentication flow to prevent cross-site request forgery attacks. This is valuable because once an exploit can automate a browser of a exposed client, it becomes necessary to kill it fast, before escalation can happen.
OAuth2 Authorization Code Flow in Practice
This particular system implemented a standard OAuth2 authorization code flow with an interesting twist for email verification:
Application → Identity Provider → Application (with code) → Token ExchangeWhat made this unique to me was how it handled unverified users. Instead of completing the flow immediately, it would pause for email verification. This meant two things. An SMTP server had to be registered, and we had to setup a corresponding email automatically. By minting MX records on the fly, we can programmatically capture inbound requests, and store everything for future use.
Multi-Step Registration Process
The registration process was split into discrete steps, each with its own validation:
- Email Collection: First endpoint accepts email and CAPTCHA token
- Password Submission: Second endpoint accepts password with complexity requirements
- Verification Trigger: Deliberately not calling the resume endpoint triggers email delivery
This separation allows for progressive validation and better user experience, as errors can be caught and displayed at each step. Breaking it down this way also in general helps me, as the simpler the problem, the easier it tends to be to solve in isolation.
Email Verification Pattern
One of the other things I was also very curious about was the email verification trigger mechanism:
if 'state=' in location:
resume_state = params.get('state', [None])[0]
# Not calling /authorize/resume here triggers the emailGetting the resume state but not calling it triggers verification
This pattern allows the application to control when verification emails are sent, rather than automatically sending them on every registration attempt. This way, they can prevent a DDOS like attack on the email servers, and keep authentication email numbers low to protect domain authority.
A second request to authorize on it's own is all that's required to continue with the authentication flow here.
Token Lifecycle Management
Once in, the system demonstrated a sophisticated approach to token management:
# Initial token (unverified status)
token_response = session.get('/api/token')
# Refresh to get updated token with verified status
refresh_response = session.get('/api/refresh_token')Token validation via refresh API
This two-step process ensures that tokens accurately reflect the user's current verification status, preventing access to protected resources before email confirmation. Tokens need to be refreshed, allowing total server side state control, and ease of blacklisting.
In the future, you may need to add fingerprint spoofing, via hardening protocols or otherwise, and rotating residential proxies to prevent flagging duplicate data mined from the browser.
Technical Implementation Details
CAPTCHA Integration
The final unique defense system was Cloudflare's Turnstile CAPTCHA as a bot prevention mechanism. The CAPTCHA token was required during registration and validated server-side:
data = {
'state': state_parameter,
'email': user_email,
'captcha': captcha_token,
'action': 'default'
}Turnstile Captcha validation payload
Prefabricated questions with high difficulty of automation, and pausing / resuming make it require a very large multimodal model operating under a rotating residential proxy for programmatic access. After this though, the token is able to mint with permissions, and the auth is a fully documented procedure.
State Machine Design
As an overview, at a high level the authentication flow follows a strict state machine pattern:
- Initial State: Unauthenticated user
- Registration Started: State parameter generated
- Account Created: User exists but unverified
- Email Verified: Verification completed
- Authenticated: Token issued with full permissions
Each transition require specific conditions to be met, ensuring security at every step. However, by maintaining cookies, utilizing a custom SMTP server capable of creating new emails on the fly, and replicating this flow with the security measures enabled, and ML systems for the captchas, it becomes possible to replicate the browser state machine.
API Authorization Pattern
Once authenticated, all API calls used standard Bearer token authorization:
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}Bearer auth protocol
The JWT token here works like a typical auth flow. It is encoded at rest, and contains claims about the user's verification status, role, and permissions, allowing for fine-grained access control.
Security Considerations
On the other side, if you are building an authentication system, it makes sense to consider some of these practices. While it is impossible to prevent automation, you can certainly de risk exploits.
CSRF Protection
The consistent use of the state parameter throughout the flow provides robust CSRF protection. Any request without the correct state is rejected, preventing malicious sites from initiating actions on behalf of users.
Email Verification Importance
The system's approach to email verification, checking the email_verified claim in JWTs ensures that unverified accounts cannot access protected resources. This pattern prevents abuse while allowing users to start the registration process easily.
Token Refresh Strategy
Finally, the token refresh mechanism serves multiple purposes:
- Updates verification status without requiring re-authentication
- Allows for token rotation for enhanced security
- Provides a way to revoke access by refusing refresh requests
Lessons Learned
This exploration revealed several best practices in modern authentication:
- Separation of Concerns: Breaking registration into multiple steps improves error handling and user experience
- State Parameter Usage: Consistent state parameter validation prevents CSRF attacks
- Progressive Enhancement: Users can begin registration easily, with verification required only for protected resources
- Token Claims: Embedding verification status in tokens allows for distributed authorization decisions
- Explicit Verification: Requiring explicit email verification prevents automated account creation abuse
Complete Flow
As some people are visual learners, I thought it might make sense to showcase how the network sequences work here.
Conclusion
Modern authentication systems are pretty complex. But OAuth2, when properly implemented with email verification and CAPTCHA protection, creates a robust and user-friendly authentication system. And abstracting that helps create the simpler experience we love in consumer systems.
Understanding these patterns is crucial for developers building secure web applications. The techniques with state parameter threading and progressive verification represent the basic standard for web application security, and I typically try to keep them in mind while building.
The key takeaway is that when automating, attention to detail is extremely important. By carefully designing the authentication flow, applications can provide both strong security guarantees and a smooth user journey, but if it was created by someone, it can be reverse engineered by someone else.
Happy Hacking!