Authentication and roles

How a user signs in, how the server verifies them, and how admin access is granted.

Accounts run on Firebase Auth: sign-up, sign-in, email verification and password recovery. The part worth understanding is not that, which is standard, but where who you are is actually checked.

The browser decides nothing

The browser holds a session and obtains a token. That token travels with every API request, and the server verifies it every time before doing anything.

No permission decision is taken on the client. Hiding a button in the interface improves the experience; it protects nothing. The protection is that the route refuses the request.

That is the difference between an application that looks secure and one that is.

How someone becomes an admin

There is no "is admin" field in the database. The permission lives in the deployment configuration: the ADMIN_EMAILS environment variable, a comma-separated list of addresses.

It works like this:

  1. The server verifies the token and reads the email.
  2. It compares that email against the list, case-insensitively.
  3. If it is not there, the request is refused. Typing the URL by hand changes nothing.

And in the interface, anyone on the list sees a button to the panel on their profile. Anyone else does not see it, and cannot get in by typing the address either.

This has a useful consequence: nobody becomes an admin by tampering with the database, because the permission is not there. To grant someone access you add their email to the variable and redeploy.

Errors do not say too much

Admin routes answer with a fixed error code, never with the internal exception message. A failure can signal that something broke, but it does not describe what or where.

A chatty error message is free information for whoever is trying the door.

Limits and abuse

Sensitive routes go through a distributed limiter in Firestore. Distributed matters: a limiter kept in process memory is useless once there is more than one instance, because each one counts on its own.

Free trials additionally carry anti-abuse protection combining a browser fingerprint and the IP, so an anonymous visitor cannot consume endlessly by opening new accounts. IP addresses are stored hashed with a salt, never in the clear.

What you have to review

  • Enable in Firebase whichever sign-in providers you want, beyond email and password.
  • Set ADMIN_EMAILS to your real addresses before deploying.
  • Tune the limits in src/config/rate-limits.ts to your expected traffic.
  • Generate your own salts: the ones shipped start with change-me-.