If your app was built with AI and it lets people log in, there's a good chance your AI-built auth has a hole in it right now. Not because the AI is bad at writing code. It's because authentication is one of those things that looks finished long before it actually is.

The login form works. You can sign in. The demo goes great. Then a real user does something you didn't plan for, or someone curious opens their browser tools and starts poking around, and the cracks show.

I review AI-built apps almost every week, and authentication is where I find the most dangerous problems. Not the loud, obvious kind that crash the app. The quiet kind, where everything looks fine until the day someone reads data they were never supposed to see. In this post I'll walk you through the three mistakes I find over and over, why the AI keeps producing them, how to check your own app, and what to actually do about it.

Why authentication fools everyone

Authentication is deceptive because the part you can see (the login form, the signup flow, the "you are logged in" state) is the easy 20%. The hard 80% is invisible. It's all the decisions about who is allowed to do what, enforced in the right place, every single time.

AI tools optimize for the part you can see. You asked for login, you got login, and in the demo it works perfectly. But the demo is the happy path: one user, valid input, nobody trying to break in. Real users are not the happy path, and attackers definitely are not.

So the app feels done. It signs you in, it shows the right screens, it even hides the admin button from regular users. And every one of those things can be true while the door is wide open underneath.

Authentication that works in the demo and authentication that's actually secure look identical until someone tests it. That's the whole problem.

Let me show you the three gaps I find most.

1. The check happens on the client, not the server

This is the big one, and it's in a huge number of vibe-coded apps.

AI tools love to put permission logic directly in the frontend, the code that runs inside the user's browser. So you get something like "if the user is an admin, show the delete button." It looks like a real security check. It is not.

The problem is that anything running in the browser belongs to the user. They can open their developer tools, change a value, and flip themselves to admin in about ten seconds. Hiding the button doesn't protect anything. The button was never the point. The action behind the button is.

A real check has to happen on the server, the part of your app the user can't touch:

// ❌ This only hides the button. The API behind it is still wide open.
if (user.isAdmin) {
  showDeleteButton()
}

// ✅ The server decides, every time, before doing anything.
export default defineEventHandler(async (event) => {
  const user = await requireUser(event)
  if (!user.isAdmin) {
    throw createError({ statusCode: 403, statusMessage: 'Forbidden' })
  }
  // ...do the protected thing
})

The difference is who you trust. In the broken version, you trust the browser to tell you the truth about who the user is. In the working version, the server checks for itself before it does anything that matters.

Here's the same problem in its most common form: data that belongs to other people. Say your app shows an order at a URL like /orders/1043. If the server fetches that order without first confirming it belongs to the logged-in user, then anyone can change the number to 1044 and read a stranger's order. This is called an insecure direct object reference, and it is everywhere in AI-built apps because the AI wrote the "fetch the order" part and never wrote the "but only if it's yours" part.

If a permission is only enforced in the UI, it isn't enforced at all.

2. Secrets are sitting in the frontend

The second mistake is leaking your secret keys into the browser.

Your app uses keys to talk to other services: your database, your payment processor, your email sender. Some keys are meant to be public (they're safe to ship to the browser). Many are not. The private ones are like the master key to your house. If one ends up in code that gets sent to the browser, anyone can grab it, even if it never appears on the screen.

This happens constantly with AI-built apps because the tools don't reliably understand the line between "runs on the server" and "runs in the browser." They'll grab a key that works and wire it into a component that ships to every visitor.

Here's the part that catches founders off guard: the key doesn't have to be visible on the page to be exposed. Everything the browser downloads can be read by the person using that browser. If the secret is in the downloaded files, it's public, full stop.

A quick way to think about it: in most modern frameworks, a variable name has to be specifically marked as public (with a prefix like NEXT_PUBLIC_ or VITE_) before it ships to the browser. The danger is when a private key gets that public prefix by mistake, or gets hardcoded straight into a frontend file.

The fix has two parts:

  • Keep secret keys server-side only. Any call that uses them happens on the server, and the browser just asks the server to do the work.
  • Rotate any key that's been exposed. If a private key has ever shipped to the browser, treat it as compromised. Generate a new one and retire the old one. Hiding it now doesn't help, because someone may already have it.

3. Sessions that never really end

The third mistake is about what happens after login. A lot of AI-generated auth signs people in just fine but handles the ongoing session badly. Two versions of this show up again and again.

Sessions that never expire. The user logs in and gets a token (a little pass that says "this is me"). In a healthy setup, that pass expires and can be revoked. In a lot of AI-built apps, it just... lives forever. So if someone's token is ever stolen, or you need to kick a user out, you can't. There's no off switch.

Tokens that are trusted without being verified. A token is supposed to be checked on every request to make sure it's genuine and hasn't been tampered with. AI sometimes generates code that reads the token and believes whatever it says without verifying the signature. That means a token can be forged. Someone crafts a pass that says "I'm the admin," and the server takes their word for it.

Both failures have the same root: the AI built the part that creates a session, but not the part that polices it over time. Creating a login is visible and easy to demo. Expiring, revoking, and verifying are invisible, so they get skipped.

The fix is to use real, battle-tested session handling instead of hand-rolled logic: short-lived tokens, a way to revoke them, and proper verification on every request. This is exactly the kind of thing you don't want an AI improvising, because the failure is silent until it's a breach.

How to tell if your AI-built auth is affected

You don't need to read a single line of code to find out whether you have these problems. You need to poke at your own app the way an attacker would. Here's a checklist you can run in fifteen minutes.

  1. Try to reach someone else's data. Log in as yourself, find a URL with an ID in it (like /orders/1043 or /users/12), and change the number. If you can see data that isn't yours, you have the client-side-check problem from section one.
  2. Check whether logout actually works. Log in, copy the page, then log out. Try to go back to a logged-in page using the browser's back button or a saved link. If you're still in, your sessions aren't really ending.
  3. Look for exposed keys. Open your browser's developer tools, go to the Network tab, reload the app, and look through what gets downloaded. Search those files for words like key, secret, or token. Anything private that shows up here is exposed.
  4. Test the hidden actions. If your app hides admin features from normal users, that's good, but it's only the UI. The real question is whether the action is protected on the server, and that one usually needs a developer to confirm.
  5. Ask where your permission checks live. If your app was built entirely with AI and nobody ever specifically added server-side authorization, assume it's missing. That's the default, not the exception.

If you run through this and find even one problem, don't panic. It means your app is normal. These gaps are the standard result of building fast with AI, and every one of them is fixable.

What to do about it

Here's how I'd prioritize, because not all of these are equally urgent.

  • Fix data leaks first. If a logged-in user can read other people's data by changing an ID, that's the one that ends in an angry email or a privacy complaint. It goes to the top.
  • Rotate and relocate exposed secrets next. Any private key that's reached the browser gets replaced and moved server-side. This is fast and high-impact.
  • Then harden sessions. Real expiry, real revocation, real verification. Less likely to be exploited tomorrow, but a serious risk you don't want lingering.

Some of this you can do yourself. Running the checklist, turning on billing and security alerts, replacing a leaked key in a settings dashboard: those are within reach for a non-technical founder.

But the core fix, making sure every protected action checks permission on the server, every time, is detail work where being 90% done means being 0% secure. One missed endpoint is the whole vulnerability. And here's the trap I see most: founders paste the problem back into the same AI that created it and ask for a fix. That's like asking it to grade its own homework. It repeats its own blind spots, and often patches the visible symptom while leaving the real hole open.

This is the part where a human who has seen these patterns reads every route, finds the gaps, and confirms each one is actually closed. Not guessing. Checking.

If you've built something with AI and people are logging into it, the uncertainty itself is the problem. You can't tell what's solid and what's one curious user away from a breach. That's exactly what I do: I read through AI-built auth line by line, find these holes, and either fix them or hand you a clear list of what needs to happen. If that would let you sleep better, let's talk about what your app needs.