Security · October 8, 2026 · 5 min read

Authentication mistakes AI app builders make (and fixes)

Login screens are one of the first things AI builders generate, and they usually look great. The sign-in form works, the dashboard appears, the logout button logs you out. What's harder to see is whether the app actually checks who you are when it matters.

These are the authentication and authorization mistakes that show up most often in apps built with Lovable, Bolt, Cursor and Replit. Each one comes with a way to check your own app and a fix.

1. Protecting pages instead of data

The most common pattern: the app redirects signed-out users away from the dashboard, and that's the only protection. The page is hidden, but the API or database behind it answers anyone who asks.

How to check: sign out, open developer tools, and replay a request the dashboard makes from the Network tab. If data comes back, the protection is cosmetic.

Fix: every API route, server action and database query must check the session itself. With Supabase or Firebase, that means row-level security or security rules. With your own backend, it means verifying the user on every request, not just on page load.

2. Trusting a user ID sent by the client

Code like getOrders(userId) where userId comes from the browser is a classic hole. An attacker changes the ID and reads someone else's orders. This is called an insecure direct object reference, and AI-generated code produces it often.

Fix: never accept the user's identity as a parameter. Read it on the server from the verified session or token, then use that value in queries.

// Risky: the client decides who it is
const orders = await db.orders.findMany({ where: { userId: body.userId } });

// Better: the server decides, from the verified session
const user = await getVerifiedUser(req);
if (!user) return new Response("Unauthorized", { status: 401 });
const orders = await db.orders.findMany({ where: { userId: user.id } });

3. Decoding tokens without verifying them

A JWT can be decoded by anyone, because the payload is only base64 encoded. Decoding is not the same as verifying. Code that decodes a token and reads the user ID, without checking the signature, will accept a token anyone wrote by hand.

Fix: use your auth provider's server-side helper to validate the token. In Supabase, call auth.getUser() on the server rather than trusting the session read from storage, since getUser() checks the token with the auth server. Projects using asymmetric signing keys can use getClaims(), which verifies the signature. In Firebase, use verifyIdToken() from the Admin SDK. For your own JWTs, use a library's verify function with a pinned algorithm.

4. Roles the user can edit

If the app checks isAdmin or role from a profile row or user metadata that the user can update, anyone can promote themselves. Try it: find where your profile is saved and see whether a role field rides along in the request.

Fix: store roles somewhere only trusted code can write. In Supabase, use app_metadata or a separate roles table with no client write policy. In Firebase, use custom claims set from the Admin SDK. Then check the role on the server or in database rules, not in the React component.

5. Weak password reset and email flows

  • Reset links that never expire or can be reused. Use your provider's built-in reset flow rather than a custom one.
  • Different error messages for "no such user" and "wrong password," which let attackers find out who has an account.
  • Email changes that take effect without confirming the new address, making account takeover easier.
  • Redirect URLs that accept any domain. Set an allowlist of redirect URLs in your auth provider's settings.

6. Open doors at sign-up

AI builders often switch off email confirmation during development to make testing faster, and it never gets switched back on. Combined with no rate limiting, this lets a script create thousands of accounts, abuse free trials or run up your AI and email bills.

  1. Turn email confirmation back on before launch.
  2. Check rate limits on sign-up, sign-in and password reset in your provider's settings.
  3. Add a CAPTCHA or bot protection to sign-up if your app gives away anything with real cost, like AI credits.
  4. Disable sign-up methods you don't use, such as anonymous sign-in or providers left on from a template.

7. Sessions that outlive their welcome

Logout should end the session, not just clear the screen. Check that signing out actually invalidates the session with your provider, and that tokens stored in the browser are removed. For apps with sensitive data, consider shorter session lifetimes and offer a "sign out of all devices" option.

Also check where tokens are stored. Many client SDKs keep them in local storage, which is readable by any script on the page. That's an accepted trade-off for many apps, but it makes cross-site scripting bugs more dangerous, so avoid rendering raw user HTML and be careful with third-party scripts.

A five-minute test you can run today

  • Create two accounts, A and B.
  • Signed in as B, try to load A's data by changing IDs in URLs and requests.
  • Signed out, replay requests the dashboard makes.
  • Try saving your own profile with an added role or plan field.
  • Request a password reset for an email that doesn't exist and compare the message.

If any of these tests returned data it shouldn't, a Deeraf Tech Check will map every auth gap in the app and rate each one by severity, so you know what to fix first.

Keep reading

Want a second pair of eyes on your app?

Book a Tech Check