Skip to main content
友田 陽大
Running the app you built with AI
バイブコーディング
セキュリティ
認証・認可
個人開発
Next.js

Is the login AI built for you real? — "looks logged in" versus a real permission check

You have a login screen, and other people's data is still visible — the most common vulnerability in AI-generated apps, explained for non-engineers. How to confirm it using nothing but a browser, why OWASP ranks it the number one API risk, and an instruction to paste into your AI tool.

Published
Reading time
7 min read
Author
友田 陽大
Share

There is a login screen. It asks for a password. Nothing displays unless you log in.

And still, changing one digit in a URL reveals someone else's data — the most common vulnerability in apps built with AI.

What's more, you can never notice it while using your own account. It works flawlessly.


1. Authentication and authorization — two different jobs

Let's sort out the words first. These two being conflated is the root of the problem.

Authentication = establishing who you are. That's the login screen. An email address and password establish "this really is that person."

Authorization = establishing whether you may do this. May the logged-in user A view order number 124, which belongs to B? That is a separate decision.

By analogy, authentication is showing your badge to enter the building; authorization is which rooms you may enter once inside. Getting through the entrance doesn't mean you may walk into the CEO's office.

What AI omits is almost always authorization.

The reason is simple: authentication is a visible feature that gets built when you ask for "a login feature." Authorization renders nothing on screen. It looks identical whether it is implemented correctly or not at all.


2. What actually happens — a concrete case

Say your app has an order detail screen.

https://your-app.com/orders/123

123 is your order number. Now change it to 124.

https://your-app.com/orders/124

If someone else's order appears, that is the vulnerability.

OWASP — the standard body for web security — classifies this as API1:2023 Broken Object Level Authorization (BOLA) and ranks it first among API risks. First. The most common, and the highest impact.

"I don't render links to other people's orders, so it's fine" — the most common misconception.

Even with no link rendered, typing the URL sends the request. Open the developer tools and the shape of your app's requests is visible too, so constructing the URL is something anyone can do.

"Not on screen" does not imply "not reachable." The UI is decoration, not a lock.


3. Why AI omits it — the list/detail asymmetry

Here's the interesting part: AI filters the list view correctly.

Asked to "show a list of the logged-in user's orders," AI genuinely applies the "of the logged-in user" condition. So the list contains only your own orders.

But asked to "show the order detail," it tends to write code that just looks up the number.

list:    "fetch all the orders belonging to the logged-in person"  → correctly scoped
detail:  "fetch order number 123"                                   → never checks whose it is

The detail view has no "of the logged-in user" condition attached. Nobody asked for one.

And — because the list is correctly filtered, normal testing never reveals it. As long as you open the list with your own account and click through to a detail, you only ever see your own data.

That asymmetry is why this vulnerability is mass-produced in AI-built apps.

The more dangerous variants

The same problem occurs not just on reads but on updates and deletes.

  • Deleting someone else's post
  • Rewriting someone else's profile
  • Cancelling someone else's order

The impact is larger than a read, and these get checked less often.


4. How to check for yourself — a browser is enough

You can confirm this without reading a line of code. Do it once before launch, without exception.

The procedure

  1. Create two test accounts (A and B)
  2. Log in as A and create some data — a post, an order, a note, anything
  3. Open that record's detail screen and note the URL. It will contain a number or an ID
  4. Log out and log in as B
  5. Type the noted URL straight into the address bar

The verdict

  • A's data appeared → vulnerability. Go to the fix instruction in this article
  • Error, 404, or "you don't have permission" → working correctly

Going further: try updates and deletes

Reads being protected doesn't mean updates are. While logged in as B, also try the edit screen for A's data — a URL like /orders/123/edit.

For one more step, try the same URL while logged out. Being redirected to the login screen is correct. If the data appears, even authentication isn't working.


5. What the correct implementation is

It isn't complicated. When you fetch the data, always include "does this belong to the logged-in user" as a condition. That's all.

[dangerous] look up by number alone
  "fetch order number 123"

[correct] look up by number + owner
  "fetch order number 123 that is also owned by the currently logged-in person"

In the correct form, specifying someone else's order number returns no match. "Not found" and "not permitted" produce the same outcome, so the existence of the number can't be inferred either.

An important principle: hiding is not defending

Hiding a button in the frontend, removing an item from a menu — these are usability touches, not defences.

The decision must happen on the server. Browser-side code can be rewritten freely by the person using it.

The relationship with RLS — make it two layers

If RLS is configured correctly, the same defence applies at the database layer too. So is the server-side check unnecessary?

It is not. They cover different ground.

  • RLS = the database layer. Applies no matter which route the query arrives through
  • Server-side check = the application layer. Can express business rules

Administrative operations that use the service_role key, for instance, bypass RLS entirely — and there only the server-side check protects you. Conversely, if AI adds a new API and forgets the check, RLS stops it.

Having both is the correct state. The design is that when one is missing, the other catches it.


6. Admin features — one more layer of trap

Admin screens (all users, delete, refund) need separate care.

Common mistakes

Only hiding the admin link in the menu. Anyone who knows the URL walks in.

Deciding "is this an admin" in the browser only. Rewrite the state from the developer tools and you're through.

Storing the role somewhere the user can edit. If the is_admin column on the users table can be updated by that user under your RLS policy, they can make themselves an administrator.

The correct shape

  1. Decide admin status on the server
  2. Verify permissions at every entry point of every admin API (one gap and it's meaningless)
  3. Keep the role information somewhere the user cannot rewrite — a database column with write-denied RLS, or a custom claim in the ID token

7. The instruction to paste into your AI tool

To hand both the checking and the fixing to an AI:

Find every handler in this app that takes an ID and returns, updates or deletes
data (Route Handlers, Server Actions, APIs, page data fetching), check each one
against the following, and report as a table.

(1) Whether the server verifies that the record belongs to the logged-in user
(2) If it does not, what happens when someone else's ID is supplied
(3) Which places merely hide things in the frontend

Then add an ownership check everywhere it is missing. Put the owner condition
into the data-fetching query itself rather than filtering afterwards in
application code. Cover updates and deletes, not just reads.

Also list every admin-only feature and confirm each performs its permission
check on the server. Check whether the value that determines the role can be
rewritten by the user.

I cannot read code, so explain in one plain sentence per finding what was
possible before the fix.

8. After fixing, check again

After the fix, redo the procedure in section 4.

The AI will report "fixed." Whether it actually took effect is a separate question. Log in as B, open A's URL. An error means it's closed.

This takes one minute and is the only way to determine whether the fix genuinely landed. "The AI said it fixed it" is not something to trust in this area.


Summary

  • Authentication (who) and authorization (may you) are different jobs. AI omits authorization
  • One digit changed in a URL revealing someone else's data is the textbook hole OWASP ranks first among API risks
  • The list is filtered correctly while the detail is wide open — that asymmetry is why testing never catches it
  • You can check with a browser alone: two accounts, open one's URL as the other
  • Hiding is not defending. Decide on the server, and pair it with RLS for two layers

Along with RLS, this is one of the four items to close before launch. The whole picture is in Before you launch the app you built with AI.

Frequently asked questions

What's the difference between authentication and authorization?
Authentication establishes who you are; authorization establishes whether you may do a particular thing. A login screen is authentication. Even once authentication succeeds, whether that user may view someone else's order is a separate decision — that is authorization. What AI omits is almost always the authorization side.
It is not. Even with no link rendered, typing the URL directly still sends the request. The developer tools also reveal the shape of the requests your app makes, so anyone can construct the URL. 'Not on screen' does not imply 'not reachable'.
How can I check this myself?
Create two test accounts. Log in as A, create some data, and note the URL containing its number or ID. Log out, log in as B, and type that URL straight into the address bar. If B sees A's data, that is the vulnerability. An error or a 'you don't have permission' message means it is working.
If RLS is configured, do I still need authorization checks?
Yes. RLS defends at the database layer and the server-side ownership check defends at the application layer — they cover different ground. Administrative operations that use the service_role key bypass RLS entirely, and there only the server-side check protects you. Having both is the correct state.
How should I protect admin features?
Decide admin status on the server and verify permissions at the entry point of every admin API. Store the role somewhere the user cannot alter — a database column or a custom claim in the ID token. Merely hiding admin links in the menu still lets anyone who knows the URL walk in.

References

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

Already live, or handling money or personal data?

An engineer reads your AI-built app and tells you what is actually dangerous

No coding knowledge required. I read the repository, list what to fix first in priority order, and hand back a report written for non-engineers plus fix instructions you can paste into your AI tool. I build production B2B SaaS with Claude Code myself, so I am not going to tell you that using AI was the mistake.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Also worth reading