Let’s be honest: AI wrote half your auth layer. Here’s what it got wrong.
There’s a pattern I keep seeing. Someone builds a full-stack app in a weekend, Cursor or Copilot fills in the auth boilerplate, it looks fine, the happy-path test passes, and it goes live.
Then, a few months later, it is support tickets, a breach, or just silence while somebody quietly has access they should never have had.
This is not about people being dumb. Smart developers with solid fundamentals fall into these traps too, especially when everything is moving fast. Vibe coding just has a way of making security mistakes feel invisible until they are very visible.
Here is the breakdown. Not only what is wrong, but why AI keeps suggesting it and what I would check before shipping.
1. Tokens stored in localStorage
Any JavaScript running on your origin can read localStorage. One XSS bug, dodgy third-party script, or compromised package can expose every token sitting there.
For browser sessions, prefer cookies with HttpOnly, Secure, and an appropriate SameSite policy. HttpOnly stops JavaScript from reading the cookie; SameSite helps reduce CSRF risk, but it is not a substitute for understanding your whole CSRF model.
2. JWTs signed with tutorial secrets
secret, your_jwt_secret_here, and copied .env.example values are not secrets. They are the first thing an attacker will try.
Generate a high-entropy secret, put it in real secret storage, and rotate it when you have reason to believe it was exposed. Never hardcode it or leave it in a sample file that reaches production.
3. Refresh tokens that never rotate
A stolen long-lived refresh token is a very calm, very persistent way into an account.
Rotate refresh tokens: issue a replacement when one is used, invalidate the old one, and treat reuse of an old token as a possible theft signal. Your auth provider may already support this; turn it on deliberately instead of assuming it is the default.
4. No protection against repeated login attempts
Without limits, brute forcing is just a loop.
Use rate limits around login, signup, password reset, and OTP verification. Add progressive delay or temporary account controls where it fits the product. Log the pattern without logging secrets, then alert when it looks abnormal.
5. Middleware added to some routes, not all of them
This one causes real damage. AI protects the route you asked it to protect. The admin endpoint you add next week or the export route you forgot about may be wide open.
Every endpoint that reads protected data or changes state needs a server-side authentication and authorisation decision. Do a route audit. Assume nothing is protected until you can point at the enforcement point.
6. Different errors for wrong email and wrong password
“User not found” and “Incorrect password” feel helpful, but together they are a free account-enumeration API.
Use a generic message like “Invalid email or password” for sensitive login flows. Think about response timing too: a different message with the same timing leak is still a leak.
7. Password reset links that live forever
Old inboxes, screenshots, forwarded messages, breached email accounts — a reset link has more ways to escape than we like to admit.
Use short expiry times, make every reset token single-use, and invalidate it as soon as it is consumed. A password-reset flow should not stay powerful forever just because somebody forgot an email from six months ago.
8. Loose OAuth redirect handling
Never accept a redirect URL because it starts with the right text. Never use open redirects around an OAuth flow.
Register exact redirect URIs, use the right flow protections such as PKCE and state where applicable, and reject everything else. This is one of those places where being “a bit flexible” becomes a vulnerability.
9. No email verification before full access
Without verification, someone can sign up using another person’s address, flood your system with fake accounts, or set up an account-takeover mess.
Send a short-lived verification link and keep unverified accounts on a limited capability set until the address is proved.
10. Logout that only clears the browser
Removing a cookie on the client is not enough if the server still accepts the corresponding session.
Invalidate the server-side session or revoke the token family when the user logs out. If your design uses self-contained tokens, be honest about the revocation trade-off and have a plan for high-risk cases.
11. Passwords stored with the wrong primitive
Plain text, MD5, and a quick SHA-256 hash are not password storage.
Use an adaptive password hashing algorithm such as Argon2id, bcrypt, or PBKDF2, with parameters chosen for your environment. Password hashing is intentionally expensive. That is the point.
12. HTTP allowed around credentials
Credentials over HTTP are credentials in the clear. “Only for development” configurations tend to leak into places they should not.
Enforce HTTPS in production, set the cookie Secure attribute, and avoid fallbacks for anything that handles credentials or sessions.
13. Role checks only in the frontend
The frontend decides what to show. The server decides what to allow.
A user can modify browser state, alter a request, or call an API directly. A client-side admin flag is a UI preference, not authorisation. Re-check permissions on every relevant server request.
14. No MFA or step-up checks for sensitive actions
One reused or phished password should not be enough for full admin access.
Require MFA for administrative users. For the most sensitive actions, ask the user to re-authenticate or perform a step-up check at the moment of the action, not only when they first logged in.
15. Demo credentials left in production
admin:admin, seeded accounts, and placeholder passwords are not convenience. They are public wordlist entries.
Search the repository, seed data, environment configuration, and CI fixtures before every production deploy. Make it a repeatable check, not a memory test.
16. Sensitive values in logs
AI loves “log the whole request body” while debugging. On an auth endpoint, that can mean passwords, reset codes, or tokens landing in places with a completely different access model.
Redact aggressively. Never log passwords, session identifiers, bearer tokens, reset links, OTPs, or raw auth headers.
17. Wildcard CORS on credentialed auth routes
Be explicit about which origins can call auth endpoints. Credentials and Access-Control-Allow-Origin: * do not belong in the same casual setup.
Review the actual browser behaviour, allowed origins, credential settings, and CSRF protections as one system. CORS is not an auth layer; it is one part of the browser boundary.
18. Treating generated auth code as trusted because it compiles
This is the root issue.
AI is trained on the internet. The internet has a lot of tutorials, demo repositories, and “get it working fast” code. The model is pattern matching against examples that were often optimised for clarity and speed, not threat resistance.
That is not a reason to stop using AI. It is a reason to be stricter with AI-generated auth code than with most other generated code.
The practical rule
Do not roll your own auth when a battle-tested provider or library fits the job. And if AI does generate an auth route, treat it as untrusted until you have manually reviewed it against a real checklist.
Ship fast, yes. But ship this one carefully.