ProductionReady
productionready
FROM PROTOTYPE TO PRODUCTION
← Blog

Authentication in AI-built apps: why it's always broken and how to fix it

By Herbert Samuels · Founder of ProductionReady.co. Thirty years in software development, architecture, cloud infrastructure, and IT management across marketing technology, advertising, healthcare, and financial services.

If an AI-built application only gets one area of security attention, authentication is where I would spend it. Weak authentication magnifies almost every other vulnerability in the system.

SQL injection is serious on its own; it's much worse when an unauthenticated user can reach the vulnerable path. Returning too much data is a defect; returning it without verifying who's asking can become a breach.

Authentication is therefore less about whether a login page works and more about what happens before, during and after the session.

That surrounding session behavior is where AI-built applications most often fail our reviews.

Where authentication usually fails

Every AI coding tool (Cursor, Bolt, Lovable, Claude, v0, Replit) can generate authentication. Ask for a login system and you'll get one: registration form, login page, password hashing, token issuance. It will work in testing. Users can create accounts and log in.

The authentication itself isn't usually the problem. The problems are in the details that surround it, the parts that don't affect whether login works but determine whether login is secure.

Problem 1: Tokens that live forever

The most frequent authentication finding across all AI-built apps is JWT tokens without expiration. The AI generates a token at login and issues it to the client. That token works until the heat death of the universe.

Why this matters: if a token is ever compromised (through XSS, a stolen device, a logged URL, a shared computer), the attacker has permanent access. Changing the password doesn't help. Logging out doesn't help (because "logging out" usually just deletes the token from the browser, not from the server's acceptance list). The only remediation is rotating the JWT secret, which invalidates every active session for every user.

Recommended change: Set token expiration to a reasonable duration: 15 minutes to 1 hour for access tokens. Implement refresh tokens with longer expiration (days to weeks) stored in HTTP-only cookies. Build a token refresh flow that issues new access tokens without requiring re-authentication. When a user changes their password or an admin revokes access, invalidate all associated refresh tokens.

Problem 2: Tokens stored in localStorage

AI tools default to storing authentication tokens in the browser's localStorage. It's the simplest approach (localStorage.setItem('token', jwt)), and it works without any additional configuration.

localStorage is accessible to any JavaScript running on the page. If the application has an XSS vulnerability, injected script can read the stored session token and send it elsewhere.

Recommended change: Store tokens in HTTP-only cookies with the Secure and SameSite flags set. HTTP-only cookies are inaccessible to JavaScript, which means XSS attacks can't extract them. The browser sends them automatically with every request, so the frontend code actually gets simpler.

Problem 3: No rate limiting on login

AI tools never add rate limiting to authentication endpoints unless specifically asked. This means an attacker can attempt thousands of login combinations per second, a brute-force attack that's trivial to automate and remarkably effective against weak passwords.

Most AI-generated applications also lack account lockout and don't log failed authentication attempts. A brute-force attack can therefore continue for a long time without detection or meaningful friction.

Recommended change: Add rate limiting to the login endpoint: five to ten attempts per minute per IP address is a reasonable starting point. Implement progressive delays after repeated failures. Log every failed authentication attempt with timestamp, IP address, and the account targeted. Optionally, implement account lockout after a threshold of failed attempts, with automatic unlock after a cooling period.

Problem 4: Password reset is the backdoor

AI-generated password reset flows are consistently the weakest point in the authentication chain. We find the same issues repeatedly.

Reset tokens generated from predictable data: timestamps, sequential numbers, or user IDs hashed with a known algorithm. An attacker who understands the generation pattern can create valid reset tokens for any account.

Reset tokens that never expire. A reset link sent today works in six months. If an email account is later compromised, every historical reset link becomes an active attack vector.

Reset tokens that aren't single-use. The same link can be used to set a new password multiple times. This means an intercepted reset link provides persistent access even after the legitimate user has used it.

No confirmation of identity. The reset flow sends a link to the email address on file, but anyone who clicks the link can set a new password. There's no verification that the person clicking the link is the account owner, which matters when email forwarding, shared inboxes, or email compromise are in play.

Recommended change: Generate reset tokens using cryptographically secure random values, not derived from any predictable data. Set token expiration to 15 to 30 minutes. Mark tokens as used immediately upon first use. Invalidate all outstanding reset tokens for an account when a new one is generated. Consider requiring the current password (for logged-in password changes) or a secondary verification step (for forgotten-password flows).

Problem 5: Client-side auth checks

This is the issue that surprises builders the most, because the application appears to work correctly. Protected pages require login. Admin sections are only visible to admin users. The navigation adjusts based on the user's role.

But all of these checks happen in the browser. The server doesn't verify authentication or authorization on individual requests. It trusts that the frontend is enforcing access control. This means any user who knows how to open developer tools (or use curl, or use Postman, or use any HTTP client) can access any endpoint regardless of the frontend's restrictions.

We regularly find applications where the admin dashboard is "protected" by a React route guard that checks a role stored in localStorage. Change the value from "user" to "admin" and the full admin interface appears. More importantly, every admin API endpoint responds to the request because the server never checks.

Recommended change: Every API endpoint that returns or modifies data must independently verify the user's identity (authentication) and their permission to perform the requested action (authorization). The frontend's role is to present the correct interface, not to enforce access control. Server-side middleware should validate the session token, confirm the user exists and is active, and check whether they have permission to access the specific resource or perform the specific action.

Problem 6: No session management

AI-built apps issue tokens at login and that's the end of the session story. There's no ability to see active sessions, no way to revoke specific sessions, no mechanism for "log out everywhere," and no detection of sessions from unusual locations or devices.

For consumer applications, this is a convenience gap. For business applications handling sensitive data, it's a security gap. When an employee leaves, when a device is stolen, or when suspicious activity is detected, the inability to revoke sessions means the only option is rotating secrets, which disrupts every user.

Recommended change: Implement a session store (database table or Redis) that tracks active sessions with metadata: user ID, device info, IP address, creation time, last activity time. Provide an API endpoint that returns active sessions and another that revokes specific sessions. Automatically expire inactive sessions. On password change, revoke all other sessions by default.

The compounding effect

These weaknesses reinforce one another. Non-expiring tokens are more dangerous when they sit in localStorage, login attempts aren't throttled, password reset is weak, and the server relies on client-side access checks.

That compounding is also what makes authentication hard to evaluate with automated tools alone. A scanner can flag a missing expiration claim on a JWT. It can't tell you that the same token is stored in localStorage in an application that also has an XSS-prone search field, a password-reset flow that reuses tokens, and no server-side session revocation. Each finding looks medium-severity in isolation; together they give an attacker a clean path from a reflected script injection to permanent account takeover. Mapping that chain is architectural work, not pattern matching.

A thorough authentication pass has an outsized effect because it improves the security boundary for every protected feature. Token lifetime, server-side verification, rate limiting, password recovery and session revocation should be reviewed as one system rather than as isolated fixes.

If you have to prioritize one subsystem first, start here. Most of the application's other protections depend on it.

Free Production Readiness Check

See where your app stands in 5 minutes.

16 questions across 8 categories: authentication, secrets, data layer, API security, and more. Get a scored report with prioritized recommendations.

ProductionReady
productionready
FROM PROTOTYPE TO PRODUCTION
Security audits, hardening, and backend builds for vibe-coded applications.
Company