Authentication Done Right: The #1 Thing Vibe Coders Get Wrong
AI coding tools can generate a login page in seconds. What they can't do is build authentication that survives real users, real attackers, and real edge cases.
Authentication is the #1 thing AI coding tools get wrong. Client-side auth checks, tokens that never expire, passwords stored in plain text — here's what production-grade auth actually looks like.
Authentication is the foundation of every web application. If auth is broken, nothing else matters — not your features, not your UI, not your payment system. A broken auth system means anyone can access anyone's data, bypass your paywall, or impersonate your admin users.
It's also the single area where AI coding tools fail most consistently and most dangerously.
I've reviewed authentication in dozens of vibe-coded apps. Every single one had at least two of the six failures I'm about to describe. Several had all six. The scary part: the founders had no idea. The login page looked professional, the flow felt right, and the AI had generated clean-looking code. Under the surface, the auth was broken in ways that any motivated user could exploit.
Here are the six authentication failures I see in every vibe-coded app, why they're dangerous, and exactly what production-grade auth looks like.
Failure 1: Client-Side Authentication Checks
What it looks like: The app checks whether you're logged in using JavaScript in the browser. A React component checks a state variable, reads a flag from localStorage, or conditionally renders content based on a client-side value.
Why AI tools do this: It's the simplest implementation. AI generates React components that check isLoggedIn or user.role === 'admin' and conditionally render the page. The code works — it hides content from users who aren't logged in.
Why it's broken: Anything that happens in the browser is under the user's control. Opening developer tools and typing localStorage.setItem('isLoggedIn', 'true') bypasses the check. Navigating directly to a protected URL bypasses conditional rendering. Modifying JavaScript variables in the console grants any access level.
This is exactly what happened to Enrichlead. Their entire subscription system was enforced client-side. Users changed a browser variable and accessed paid features for free. The product shut down within 72 hours of users discovering the bypass.
The fix: Every authentication check must happen server-side. The browser sends a session token with every request. The server validates that token against the database before returning any data. If the token is missing, expired, or invalid, the server returns a 401 status — regardless of what the browser claims.
In practice, this means your API endpoints look like this: receive request → extract token from header → validate token server-side → check user permissions → return data (or reject). No API endpoint should ever return data without first validating who's asking for it.
Failure 2: Passwords Stored Incorrectly
What it looks like: Passwords stored in plain text, base64 encoding, MD5, or SHA-1 in the database.
Why AI tools do this: When asked to build a login system, some AI models generate the simplest working implementation: store the password, compare it on login. Others use basic encoding (base64) that makes passwords look encrypted but isn't. Some use deprecated hashing algorithms (MD5, SHA-1) that can be cracked in seconds with modern hardware.
Why it's broken: If your database is ever exposed — through a breach, a misconfigured backup, or an SQL injection attack — every user's password is immediately compromised. Since most people reuse passwords, a breach of your app could compromise your users' bank accounts, email, and other services.
The fix: Use bcrypt, argon2, or scrypt for password hashing. These algorithms are specifically designed for password storage: they're intentionally slow (preventing brute force attacks) and include built-in salting (preventing rainbow table attacks). In Node.js, the bcrypt package handles this in three lines of code.
Better yet, don't build password auth yourself. Use Supabase Auth, Auth0, Clerk, or Firebase Authentication. These services handle password hashing, session management, email verification, and password resets correctly by default. The AI can focus on your app's features while the auth provider handles security.
Failure 3: Tokens That Never Expire
What it looks like: JWT tokens or session cookies with no expiration time, or with expiration times set to years in the future.
Why AI tools do this: Expiring tokens add complexity — the AI would need to generate token refresh logic, handle expired session redirects, and manage the UX of re-authentication. The simplest working implementation is a token that never expires.
Why it's broken: If a token is stolen — through a shared computer, a compromised browser extension, a network interception, or any other vector — it grants permanent access. The legitimate user has no way to invalidate it. The attacker has indefinite access to the account.
The fix: Access tokens should expire in 15-60 minutes. Refresh tokens should expire in 7-30 days. When an access token expires, the client uses the refresh token to get a new one — silently, without disrupting the user experience. When a user changes their password or you detect suspicious activity, invalidate all refresh tokens for that account.
With Supabase Auth, this is handled automatically. Supabase issues short-lived access tokens and manages refresh token rotation out of the box.
Failure 4: Broken Password Reset
What it looks like: Password reset tokens that can be used multiple times, never expire, are predictable (sequential IDs or timestamps), or reveal whether an email address has an account.
Why AI tools do this: AI generates the happy path: user requests reset, gets email, clicks link, sets new password. It rarely considers the edge cases and security requirements of the reset flow.
Why it's broken: Reusable tokens mean an attacker who intercepts a reset email has permanent access to the account. Non-expiring tokens mean old intercepted emails remain valid. Predictable tokens mean an attacker can guess valid reset URLs. Account existence disclosure means an attacker can enumerate which email addresses have accounts (useful for phishing campaigns).
The fix: Reset tokens must be cryptographically random (UUID v4 or similar), single-use (invalidated after first use), time-limited (15-60 minutes), and the reset flow must not reveal whether the email address exists. The response to both "we sent you an email" and "no account found" should be identical.
Again, auth providers handle this correctly by default. Supabase Auth's password reset flow implements all of these requirements without configuration.
Failure 5: No Rate Limiting on Login
What it looks like: The login endpoint accepts unlimited rapid requests with no delay, lockout, or CAPTCHA.
Why AI tools do this: Rate limiting is middleware that sits outside the login logic. AI generates the login function — validate credentials, return token — without considering what happens when that function is called 10,000 times per minute.
Why it's broken: Without rate limiting, an attacker can attempt thousands of password combinations per minute through automated scripts. Even with properly hashed passwords, weak passwords (which many users choose) can be cracked quickly through brute force.
The fix: Implement progressive rate limiting: after 5 failed attempts, add a 30-second delay. After 10, require a CAPTCHA. After 20, lock the account for 15 minutes. Log all failed login attempts with IP addresses and timestamps for security monitoring.
In Node.js, packages like express-rate-limit and rate-limiter-flexible handle this with minimal configuration. Apply rate limiting to login, registration, and password reset endpoints at minimum.
Failure 6: Frontend Role Checks
What it looks like: Admin panels hidden behind a React conditional (if (user.role === 'admin')), feature access determined by a client-side variable, or API endpoints that trust the role the client claims to have.
Why AI tools do this: It's the same pattern as Failure 1, applied to authorisation rather than authentication. AI generates the simplest way to show different content to different users — a frontend conditional. The code works correctly in testing because the developer doesn't try to bypass it.
Why it's broken: A user can access any "hidden" admin page by navigating directly to the URL. They can call any "restricted" API endpoint by copying the request from browser dev tools and changing the user ID. They can grant themselves any role by modifying the client-side state that the AI uses for role checks.
The fix: Every API endpoint must independently verify the caller's role against the database. The frontend conditional is for UI convenience (hiding menu items the user can't use), not for security. The server-side check is the security layer.
In practice: the API endpoint extracts the user's session token, looks up the user in the database, checks their role, and only then returns data. If the role doesn't match, the endpoint returns 403 Forbidden — even if the user somehow navigated to the admin URL.
The Production-Grade Auth Stack
After 100+ production implementations, here's the auth stack I recommend for most AI-built applications.
Use an auth provider. Supabase Auth, Auth0, or Clerk. Don't build auth from scratch with AI tools — the probability of getting it right is too low. Auth providers handle password hashing, session management, token refresh, password reset, email verification, and rate limiting correctly by default.
Supabase Auth is my default for most builds. It's free for up to 50,000 monthly active users, integrates natively with Supabase's database (where most of my apps run), and includes row-level security policies that enforce access control at the database layer — the strongest possible position.
Add server-side session validation to every API endpoint. Even with an auth provider, your API must validate the session on every request. With Supabase, this means calling supabase.auth.getUser() at the start of every API handler and rejecting requests with invalid or missing tokens.
Implement row-level security (RLS) in the database. This is the belt-and-suspenders approach: even if your API has a bug that returns data it shouldn't, the database itself refuses to serve rows that don't belong to the requesting user. Supabase makes RLS straightforward with policy definitions.
Add role-based access control at the API layer. Define clear roles (user, admin, owner) and check them server-side on every request. Store roles in the database, not in tokens — tokens can be forged, but database records can't be modified by the client.
The 10-Minute Auth Audit
If you've built authentication with AI tools, here's a rapid assessment you can do right now.
Open browser developer tools. Go to your app's login page. Check the Network tab for API calls. Check localStorage and sessionStorage for auth-related keys. Check the Sources tab for auth logic in your JavaScript.
Check for client-side auth. Search your frontend codebase for localStorage.getItem, isAuthenticated, user.role, isAdmin, or any conditional that controls access to content or features. Every hit is a potential bypass.
Test role escalation. If your app has admin features, log in as a regular user. Navigate directly to admin URLs. Call admin API endpoints with your regular user's token. If any work, access control is broken.
Check password storage. Look at your database's user table. Can you read passwords? If so, they're stored insecurely. Check for columns named password, hash, pwd, or similar.
Test token expiry. Log in, copy your auth token, log out, then make an API request with the old token. If it still works, tokens aren't being invalidated on logout. Wait an hour and try again — if it still works, tokens don't expire.
If any of these checks fail, your authentication needs fixing before launch. This isn't optional — it's the difference between a product and a liability.
Frequently Asked Questions
Can I use AI tools to fix my authentication?
For specific, well-defined fixes (adding rate limiting, moving a check server-side), yes. For designing the auth architecture, no — the AI doesn't have the context to know what's secure for your specific application. The safest approach is to migrate to an auth provider like Supabase Auth and let the provider handle the security-critical parts.
Is Supabase Auth secure enough for production?
Yes. Supabase Auth is used in production by thousands of applications. It handles password hashing, session management, token refresh, and email verification correctly by default. The risk with Supabase Auth isn't the provider — it's misconfiguration. Make sure RLS is enabled, ensure your API validates sessions, and don't bypass the auth middleware for "convenience."
Do I need two-factor authentication (2FA)?
For apps handling financial data, health data, or sensitive business information, strongly recommended. For general SaaS applications, it's good to offer but not always essential at launch. Supabase Auth and Auth0 both support 2FA out of the box.
How long does it take to fix broken authentication?
If you're migrating to an auth provider (recommended), expect 3-5 days for a standard app. This includes setting up the provider, migrating existing users, updating all API endpoints to validate sessions, and implementing RLS. If you're fixing custom auth in place, expect 1-2 weeks depending on how deep the issues go.
What's the relationship between authentication and the security checklist?
Authentication covers items 9-14 of the 25-point security checklist. It's the single most important category — which is why it gets its own deep-dive guide. The security checklist covers authentication plus four additional categories (data protection, API security, infrastructure, and business logic). Both are essential reading before launch.
---