Skip to main content
ALL GUIDES
19 min readUpdated Published

Don't Vibe Code Your Login

If you aren't an experienced developer, don't build your own auth — use a provider. A 2026 audit of 200 real AI-built repos found 90% had an exploitable vulnerability, most of it in access control the login form never touches. Full checklist and audit prompt inside.

Short answer: if you aren't an experienced developer, don't build your own authentication at all. Use a trusted, battle tested provider like Supabase, Auth0, Clerk, Better Auth or Firebase. They are all free to start, and engineers have already solved these problems properly.

The reason isn't that AI writes bad code. It's that AI writes auth that works on the happy path, and you may have no way of knowing what it skipped. When you test it, it works. In an audit of 200 real vibe-coded repositories published in June 2026, 90% contained at least one exploitable vulnerability, and the largest category was broken access control, not the famous bugs like SQL injection.

If you decide to build your own anyway, or you have already shipped one, the full checklist below is what to hand to your AI. There's an audit prompt underneath it written to make the model find gaps instead of reassuring you.

Every statistic here is sourced, and where a widely repeated claim could not be traced, I say so.

The full auth checklist

Grouped by area. Each item is a thing to verify, not a thing to trust.

Passwords

  • Minimum length is 15 characters for password-only login. NIST SP 800-63B-4 raised this from 8 in its July 2025 revision. Eight is still acceptable only when the password is one factor inside real MFA.
  • Maximum length is at least 64 characters.
  • No composition rules. No forced uppercase, digits or symbols. NIST is explicit: verifiers SHALL NOT impose them.
  • No periodic rotation. Force a change only on evidence of compromise.
  • New and changed passwords are checked against a breached-password blocklist. This is a SHALL in NIST, and OWASP ASVS asks for at minimum the top 3,000 matching passwords.
  • Paste is allowed, and password managers are allowed.
  • The password is verified exactly as received. No truncation, no case transformation.
  • Changing a password requires the current password.
  • No security questions. ASVS prohibits them outright, not just discourages them.
  • Passwords are hashed with Argon2id (for example m=19456, t=2, p=1), or scrypt, or bcrypt at work factor 10+ if you are stuck with legacy. Never a plain hash, never encryption.

Brute force, credential stuffing and rate limiting

  • Consecutive failed attempts on one account are capped. NIST says no more than 100.
  • Lockout is graduated rather than binary, so an attacker cannot trivially lock out your real users. Exponential backoff starting at one second is the OWASP suggestion.
  • Counters key on the account, not just the IP. Attackers rotate IPs.
  • Rate limits exist on every one of these, not just login: signup, password reset request, verification email resend, MFA code entry, MFA push approval, token refresh.
  • Anything that triggers a paid third party (SMS, email) has spending caps and billing alerts. OWASP's API Top 10 documents an unprotected forgot-password SMS flow costing thousands of dollars in minutes.
  • If you use GraphQL, check that batched mutations in a single request cannot bypass your per-request rate limit. This is a documented bypass on login endpoints.

Sessions and tokens

  • Session IDs are generated by a CSPRNG with at least 64 bits of entropy (ASVS asks 128 for reference tokens).
  • A new session token is issued on every authentication and re-authentication, and the old one is terminated. This is the session fixation defense.
  • Both an idle timeout and an absolute timeout exist. Two to five minutes idle for high-value apps, 15 to 30 for low-risk, and an absolute ceiling regardless of activity.
  • Logout invalidates the session server-side. Clearing the cookie in the browser is not termination.
  • Cookies carry Secure, HttpOnly, and SameSite=Strict or Lax. The full recommended form is Set-Cookie: __Host-SessionID=<value>; Secure; HttpOnly; SameSite=Strict; Path=/.
  • Nothing sensitive is in localStorage or sessionStorage. Not tokens, not session IDs, not JWTs, not refresh tokens. One XSS discloses every one of them.
  • If you use JWTs: the none algorithm is rejected, accepted algorithms are hardcoded rather than read from the token header, and exp, nbf and aud are all validated.
  • Refresh tokens for public clients rotate, per RFC 9700. A refresh token that never changes is a permanent credential.
  • There is a way to invalidate a session server-side. Stateless JWTs have nothing to revoke, which is a design decision you should make deliberately, not by accident.

Password reset

  • Reset tokens are cryptographically random, single use, and expire quickly. ASVS caps comparable out-of-band requests at 10 minutes.
  • The response is identical whether or not the account exists, and takes the same amount of time. "If that email address is in our database, we will send you a reset link."
  • Reset requests are rate limited per account.
  • A notification email goes to the user after a reset. The new password is never in it.
  • Existing sessions are invalidated on reset, or the user is asked.
  • The user is not automatically logged in after reset.
  • Reset does not bypass MFA. This is the single most common account takeover chain: reset by email, get logged straight in, never get challenged for the second factor.

Email verification and email change

  • Verification links are random, single use, and short-lived.
  • An unverified account cannot be used, and more importantly cannot be linked to or matched against later.
  • Changing an email address requires full re-authentication (ASVS 7.5.1).
  • An email change sends a notice to the old address and a confirmation to the new one.
  • Signup, login and reset all return consistent messages so accounts cannot be enumerated.

OAuth and social login

  • Redirect URIs are validated by exact string matching. No wildcards, no prefix matching. RFC 9700 is unambiguous on this.
  • PKCE is used. It is a MUST for public clients under RFC 9700, and OAuth 2.1 extends that to everyone. There is no scenario in a small app where skipping it helps you.
  • The state parameter is not just sent, it is validated on return.
  • Accounts are keyed on the provider's immutable subject identifier (sub), never on email. Microsoft's own guidance: email, preferred_username and unique_name "aren't unique and can be controllable" and are unsuitable for authorization decisions.
  • Auto-linking by email is off, or gated on your own local verification state. This is the live 2026 attack. CVE-2026-53516 (CVSS 8.3, May 2026) let an attacker pre-register a victim's email, wait for the victim to sign in with Google, and capture the account. Setting requireEmailVerification: true did not save you. Better Auth patched it in 1.6.11. The same class hit Authorizer and Nhost.
  • Access tokens are never passed in URL query parameters.
  • The implicit grant and the resource owner password credentials grant are not used. The latter is a MUST NOT.

MFA

  • Codes are rate limited on entry. A six digit code with unlimited attempts is not a second factor.
  • Push-based MFA is rate limited against push bombing (ASVS 6.6.4).
  • Codes are single use, and the consumed counter is recorded so the same TOTP cannot be replayed inside its validation window.
  • TOTP lifetime is 30 seconds, out-of-band codes 10 minutes maximum.
  • Backup codes are hashed at rest like passwords (ASVS 6.5.2 requires this for anything under 112 bits of entropy). Almost everyone stores these in plaintext.
  • Disabling MFA, or changing the associated email, requires re-authentication.
  • The real session is issued only after the full factor chain completes. If you issue a session after the password step and "upgrade" it after the OTP, your MFA is advisory.
  • Consider offering a phishing-resistant option. NIST 800-63B-4 makes at least one phishing-resistant option a SHALL at AAL2, and passkeys are the practical answer.
  • SMS is a restricted authenticator under NIST, not a banned one. If you use it, offer something better alongside.

Authorization, which is the actual number one

  • Every access control check happens server-side. A hidden nav item and a wrapped React route are user experience, not security.
  • Checks are per object, not per type. Having access to invoices does not mean having access to invoice 4471.
  • Queries are scoped to the authenticated user at the source: current_user.orders.find(id), never Order.find(id).
  • The tenant or org identifier comes from the trusted session, never from a request body or a client-influenced claim.
  • Default is deny, including for every new route you add later.
  • Users cannot mass-assign fields they should not control. Sending role: "admin" or is_paid: true into an update should not work even when they own the record.
  • Responses do not return fields the user should not see.
  • If you use Supabase: RLS is enabled on every table in an exposed schema, and policies are written and tested. Note that auth.uid() returns null for anonymous requests and null = user_id is false, so policies can silently do nothing. Write auth.uid() IS NOT NULL AND auth.uid() = user_id.
  • Test every private page while logged out, and while logged in as a different user.

Secrets

  • No API keys, service keys or credentials in frontend code, in the repo, or in the bundle.
  • You understand the difference between your publishable key (public by design, safe in a browser, guarded only by RLS) and your secret or service-role key (bypasses RLS entirely, backend only, never in a browser even on localhost).
  • If a key leaked, you rotated it. Deleting the commit is not remediation. GitHub's own guidance says rotate first, history rewriting is usually unnecessary.
  • Secret scanning runs on the repo, and ideally pre-commit.
  • Environment variables have no publicly known default fallbacks.

For scale on that last section: GitGuardian found 28.65 million new hardcoded secrets in public GitHub commits during 2025, up 34% year on year, the largest single-year jump they have recorded. Secrets for AI services specifically rose 81%.

Logging

  • Authentication successes and failures are logged. So are authorization failures, which most small apps never log at all, which is exactly why the number one attack class stays invisible.
  • Also logged: session management failures, user administration actions, privilege changes, access to sensitive data.
  • Never logged: passwords, session IDs, access tokens, API keys, connection strings, personal data beyond what you need. Dumping a request object on error can turn your logging SaaS into a credential store.
  • Logs have an alert path. OWASP renamed this category from "Monitoring" to "Alerting" in 2025 precisely because logging into a void is itself the failure.
  • User-controlled strings are encoded before being written to logs, to prevent log injection.

The prompt

Paste this into whatever AI you are building with, along with your auth code. It is written to make the model audit rather than reassure, because a model asked "is this secure?" will usually say yes.

text
You are doing a security audit of the authentication and authorization code in
this project. Your job is to find what is missing or wrong, not to reassure me.

Rules for this audit:
- Do not tell me the code looks good. Assume something is wrong and find it.
- For every item below, respond with one of: PRESENT (and show me the code that
  implements it), MISSING, or INCORRECT (and explain what breaks).
- If you cannot find the relevant code, say MISSING. Do not assume a framework
  handles it for me unless you can point at the configuration that proves it.
- At the end, rank everything you found by real-world exploitability, and give
  me the specific fix for the top five.

Audit against this list.

AUTHORIZATION (check this first, it is the most commonly broken)
1. Is every access control check performed server-side, not only in the UI?
2. Is authorization checked per specific object, not just per object type?
   Show me where the query is scoped to the authenticated user.
3. Can a user change an ID in a request and access someone else's record?
4. Does the tenant or organisation ID come from the trusted session, or from
   client-controlled input?
5. Can a user mass-assign fields they should not control, like role or
   is_admin or is_paid?
6. Do any API responses return fields the requesting user should not see?
7. Is the default deny, including for routes added recently?
8. If this project uses Supabase or similar: is RLS enabled on every table in
   an exposed schema, are the policies correct, and do they handle the case
   where auth.uid() is null?

AUTHENTICATION
9. Minimum password length of 15 characters for password-only login?
10. Any composition rules or forced periodic rotation? Both should be absent.
11. Are new passwords checked against a breached-password blocklist?
12. How are passwords hashed? It should be Argon2id, scrypt, or bcrypt at
    work factor 10 or higher.
13. Is the current password required to change a password?
14. Are there security questions anywhere? There should not be.

RATE LIMITING
15. List every endpoint that has rate limiting, and every one that does not.
16. Specifically check: login, signup, password reset request, verification
    resend, MFA code entry, MFA push, token refresh.
17. Are failed attempts capped per account, and is lockout graduated rather
    than permanent?
18. Does anything trigger a paid third party (SMS, email) without a cap?

SESSIONS
19. Is a new session token issued on every login and re-authentication, with
    the old one terminated?
20. Do both an idle timeout and an absolute timeout exist?
21. Does logout invalidate the session server-side, or only clear a cookie?
22. Are cookies set with Secure, HttpOnly and SameSite?
23. Is any token, session ID or JWT stored in localStorage or sessionStorage?
24. If JWTs are used: is the "none" algorithm rejected, are the accepted
    algorithms hardcoded, and are exp, nbf and aud all validated?
25. Do refresh tokens rotate?

PASSWORD RESET AND EMAIL
26. Are reset tokens random, single use, and short-lived?
27. Do the responses reveal whether an account exists, through either the
    message or the response time?
28. Can the reset flow bypass MFA?
29. Is the user auto-logged-in after reset? They should not be.
30. Does changing an email address require re-authentication, and notify the
    old address?
31. Can an unverified account be used, or be matched or linked against later?

OAUTH AND SOCIAL LOGIN
32. Are redirect URIs validated by exact string match, with no wildcards?
33. Is PKCE used?
34. Is the state parameter validated on return, not just sent?
35. Are accounts keyed on the provider's immutable subject ID, or on email?
    Email is wrong.
36. Can an OAuth identity auto-link to an existing local account matched only
    by email address? This is an account takeover vector.

MFA
37. Are MFA code entry attempts rate limited?
38. Are codes single use, with the consumed counter recorded?
39. Are backup codes hashed at rest?
40. Is the real session issued only after the full factor chain completes?

SECRETS AND LOGGING
41. Are any API keys, service keys or credentials present in frontend code,
    in the repo, or in the built bundle?
42. Is a service-role or secret key used anywhere it could reach a browser?
43. Are authorization failures logged, not just authentication failures?
44. Is anything sensitive being written to logs: passwords, tokens, session
    IDs, full request objects?

Now audit the code and report.

Two things about using this. Run it against a real codebase, not a description of one. And when it comes back with MISSING items, fix them one at a time and re-run, because fixing one thing is exactly the situation where a model removes a check to make something else work.


Why each of these matters

The rest is the evidence behind the checklist: what the research found, what has already gone wrong in public, and why AI specifically misses this category.

What the research actually found

The most useful study on this came out in June 2026. Three researchers, one independent and two from Microsoft UK and CISPA, collected 10,517 real open-source vibe-coded repositories from GitHub, randomly sampled 200, and audited them properly with two-author human validation.

180 of those 200 repositories, so 90%, had at least one exploitable vulnerability. The median vulnerable repo had seven. Of the 1,471 confirmed vulnerabilities, 20% were rated Critical and 56.7% High.

Then look at where the vulnerabilities clustered.

Broken Access Control was the single biggest category: 530 findings, 36% of everything, present in 75.5% of the repos. OWASP's own baseline incidence for that category in human-written applications is 3.74%. Authentication Failures showed up in 42.5% of repos against a human baseline of 2.92%.

And 82.8% of those broken access control bugs were in backend code. This is not simply "they forgot to add a check on the server." The server-side logic itself was wrong.

One more finding worth sitting with: smaller repositories were denser in vulnerabilities, 1.62 per thousand lines of code for repos under 2,000 lines, against 0.06 for repos over 100,000. Your small weekend app is not safer because it is small.

The pattern nobody expects

In December 2025, a security firm called Tenzai ran a controlled comparison. Same prompts, same three apps, built five times over with Claude Code, OpenAI Codex, Cursor, Replit and Devin. Fifteen apps total.

They found 69 vulnerabilities. Six were critical. And across all fifteen apps there was not one exploitable SQL injection or XSS bug.

Every serious finding was in API authorization logic and business logic.

That is the whole story in one result. The models have thoroughly learned the famous vulnerabilities. Parameterized queries, output escaping, the stuff that has been in every tutorial for fifteen years. What they have not learned is the boring question: does this specific user own this specific row?

ETH Zurich's BaxBench benchmark found the same shape from a different angle. Roughly half of the backend solutions that were functionally correct were still exploitable. The best model they tested produced code that was wrong or vulnerable 62% of the time.

Working code and safe code are different tests, and AI is being graded on the first one.

A note on the number everyone quotes

You will see "45% of AI-generated code fails security tests" in almost every article on this topic, cited to Veracode's 2025 GenAI Code Security Report. The number is real and the report is good.

It also does not test authentication or access control at all. It covers XSS, log injection, SQL injection, weak crypto. Using it as an authentication statistic is a mistake, and I would rather point you at the studies that actually measured the thing.

Three times this went wrong in public

Lovable, May 2025. Apps generated on the platform were shipping without Row Level Security enabled on their Supabase tables, which meant the public anon key worked as an admin credential. Security researcher Matt Palmer scanned the platform's own showcase and found 170 projects with 303 vulnerable endpoints, around 10.3% of those analyzed. Emails, phone numbers, API keys, payment details, all readable by anyone. It became CVE-2025-48757 with a CVSS score of 9.3. Worth noting: Lovable disputes the CVE, arguing customers own their app's data security.

Base44, July 2025. Wiz Research found that two API endpoints on the Wix-owned platform, user registration and OTP verification, required no authentication whatsoever. If you had an app_id, and app_ids were sitting in public URLs and manifest files, you could register yourself a verified account on somebody's private app and walk straight past their SSO. Internal chatbots, HR systems, knowledge bases. Patched in 24 hours.

Moltbook, February 2026. A hardcoded Supabase publishable key in client-side JavaScript, plus no RLS policies, equals unauthenticated read and write access to the production database. Wiz found 1.5 million agent API tokens, 35,000 email addresses, and 4,060 private DMs, some containing other people's plaintext OpenAI keys. The founder had posted publicly: "I didn't write a single line of code for @moltbook."

Two independent scans give a sense of how common this is. Symbiotic Security scanned 1,072 Supabase-backed vibe-coded apps in June 2026 and found 98% had at least one flaw, 16% had critical ones, and 26 sites were clean. Escape.tech scanned 5,600+ apps in October 2025 and found 2,038 critical vulnerabilities, 400+ exposed secrets and 175 instances of exposed personal data including medical records and bank details.

Both were passive scans, so both are floors, not ceilings.

Why AI specifically misses this

The June 2026 study grouped root causes, and the categories explain the mechanism better than any theory.

Forgotten obligations. The model writes a placeholder and moves on. One shipped, live application had a sign-in route containing the comment // TODO: Add password verification here followed by "For now, just log them in."

Demo-oriented design. It builds what demos well. OAuth tokens dropped into browser localStorage with base64 encoding treated as protection.

Incomplete change propagation. You add a feature, and the new handler quietly lacks the auth and rate-limit calls that its sibling handlers all have. Or the middleware authorizes every logged-in user for /admin regardless of role.

Function-fix side effects. This is the nastiest one. Something breaks, you tell the AI to fix it, and it fixes it by removing the obstacle. A login route that issues a valid session cookie to any caller regardless of credentials. An upload route added to the authentication bypass list to make uploads work. A researcher found a login bypass introduced with the comment // Hardcoded check since Supabase Auth is broken.

Read that last one again. The AI hit a wall with real authentication and routed around it, and the app shipped that way.

None of this is the model being stupid. It is the model optimizing for the thing you asked for, which was an app that works.

Auth is bigger than a login form

Login is one screen. Authentication is the whole system, and every piece below is a place people have lost accounts.

Sign up. Sign in. Sessions and tokens. Password resets. Email verification. Email change. Social and OAuth login. MFA enrollment, and MFA recovery. Rate limiting on all of it. And underneath all of that, authorization, which is the actual number one, and which is a different question from authentication entirely.

Authentication asks who you are. Authorization asks what you are allowed to touch. OWASP's 2025 Top 10 puts Broken Access Control at A01 and Authentication Failures down at A07, and reports that 100% of applications tested had some form of broken access control.

The login form is not where the risk lives.

Just use a provider

Everything in that checklist is solved. You do not need to build any of it.

Free tierSelf-hostableNotes
Supabase Auth50,000 MAUYes (MIT)Leaked-password protection is Pro only. RLS is on you.
Auth025,000 MAUNoMost complete out of the box. Brute-force protection on by default.
Clerk50,000 MRUNoBest prebuilt UI. MFA and passkeys are paid-tier only.
Better AuthUnlimitedYes (MIT)Acquired by Vercel in July 2026, still open source.

A few real caveats, since "just use a provider" is not the end of the thinking:

Supabase enables RLS by default for tables created in the dashboard, but not for tables you create in raw SQL. And there is a dated change coming: from 30 October 2026, tables stop being automatically exposed to the Data API on existing projects, so plan for it.

Better Auth disables rate limiting in development and stores rate limit data in memory by default, which on serverless means it is effectively not there across cold starts. Switch it to database or secondary storage. Also, run 1.6.11 or later for the OAuth linking fix.

Clerk does not enable bot protection automatically, and its Native API setting bypasses CAPTCHA challenges entirely while enabled.

Auth0 runs bot detection in monitoring mode, doing nothing, if you enable it without configuring response actions.

The point is not that providers are magic. It is that a provider gets you a well-tested implementation of forty hard things, and leaves you responsible for about four configuration decisions instead of forty implementation decisions.

What I would actually do

Use a provider for authentication. Then spend your own attention on authorization, because that is the category that showed up in 75.5% of audited AI-built repos, that OWASP found broken in some form in 100% of applications tested, and that no provider can do for you. Only you know which user is allowed to see which row.

And test your app logged out. Then test it logged in as somebody else. That single habit would have caught most of what is in this article.


Sources

Research on AI-generated code

Incidents

Standards

Provider documentation

Developer surveys

Claims that circulate widely but that I could not trace to a primary source are deliberately not in this article. That includes the frequently repeated assertion that the Tea app breach was AI-built, for which the breach is well documented but the AI attribution is not.