Stage 2 · First Version · Chapter 11

SaaS Login Security: Why Every Auth Rule Belongs on the Server

Removing a signup button is not the same as disabling signup. Learn how to enforce login, employee accounts, password limits and sessions on the server.

Dr. Yahia Elkhouli · October 30, 2026 · 6 min read

On May 26, 2026, a new company signed up for KashBoss using an email address and a password. That should have been impossible. Three weeks earlier I had removed email signup completely, and our registration screen showed one button only: continue with Google. I asked my AI assistant how this person had signed up with email.

The answer was uncomfortable. We had removed the email form from the screen, and only from the screen. The signup code still existed inside the app, and the email signup switch in our authentication service was still on. Anyone with an older saved copy of the web app, or anyone calling the login service directly, could still create an account. The lock existed only where it was visible.

Logins feel like a screen problem: a form, a button, an error message. They are really a server problem.

The short answer

Every login rule that matters, such as who can sign up, who can log in and how many password guesses are allowed, must be enforced on the server, because the screen is only one of the ways in. Hiding a signup form does not disable signup. Turn the feature off in your authentication settings, delete the unused code, and limit wrong password attempts where a direct caller cannot skip the limit.

Login basics in plain words

Authentication means proving who you are. Authorization means deciding what you may do once in. This article covers the first.

There are three common ways to log in: email and password, "Sign in with Google" (Google confirms the person and you never store their password), and magic links or passkeys, which avoid passwords entirely. Passwords are never stored as typed. The system stores a hash, a one-way fingerprint, and compares fingerprints at login. A login service such as Supabase Auth does this for you, and you should let it.

After login, the app receives an access token, a short-lived pass valid for about an hour, and a refresh token, a longer-lived ticket for getting a new pass. Picture a festival wristband that expires every hour, and a ticket in your pocket to collect a new one.

The screen is a suggestion. The server is the law.

How we did it in KashBoss

Disable on the server, not on the screen

THE MISTAKE WE MADE

We "disabled" email signup by removing the form from the screen. The signup functions were still in the app's code, and new email signups were still allowed in our authentication settings. A real company signed up by email weeks after I believed it was impossible.

The fix was to turn off new email signups in the authentication settings (keeping email login for existing users) and delete the unused signup code, so nobody can re-expose it by accident. Now, whenever someone says "that feature is off," we ask one question: off on the screen, or off on the server? We chose Google-only signup in the first place because a mistyped email is worse than it looks. That customer can never receive a password reset and cannot be contacted. Google gives verified addresses and faster signup.

Employee accounts have their own rules

In business software, employees usually do not sign up. The owner creates each account with a name, email, first password and role, and the employee must change that password at first login. Each rule came with a bug:

The biggest lesson was this one.

THE MISTAKE WE MADE

Employees used to log in before they had any session. To check the password, the app read the employees table directly as an anonymous visitor and compared the fingerprint on the device. For that to work, a database rule let anonymous visitors read the employees table. Anyone with the app's public key could read every employee of every company: names, emails, phone numbers and password fingerprints. An external review found it on June 1, 2026.

We moved the password check into a server function that returns the employee's details without the fingerprint. We released that first, confirmed employees could still log in, and only then removed the open rule. Build the new door, test it, then close the old one. Later we added a guessing limit on the server, since the app's limit on the device did not stop anyone calling the server directly.

THE PROMPT
Review every way a person can sign up or log in to [APP NAME]: owners, employees, Google, email.
For each one, tell me:
1. Where the check happens: on the screen, in the app's code, or on the server.
2. Whether anything about other users (emails, phone numbers, password data) can be read before login.
3. Whether there is a limit on wrong password attempts on the server, not only in the app.
4. What happens if someone calls the login or signup service directly, without our app.
List every rule that exists only on the screen, then propose the server-side version of each.

Never log people out by surprise

In August 2026 I gave a simple instruction: the app must never log anyone out unless they press the logout button. Being thrown out in the middle of work is especially painful for a sales rep standing in a customer's shop. We found six separate paths that logged people out without their choice. A network failure while loading user details sent them to the login screen even though a saved copy sat on the device, and the login library announced "signed out" after one failed renewal.

One case was strange. A customer was logged out every time he imported a product list. His computer clock ran about an hour ahead, so the login library believed his pass was always about to expire and renewed it every ten seconds. Each renewal cancels the previous pass, and during an import some requests carried a canceled pass, so the server refused them and the library logged him out. The clue: his phone worked, his laptop did not.

Treat "could not check" differently from "no." A deleted employee used to keep working on his device because "the server could not be reached" and "this employee no longer exists" came back as the same empty answer. Now the server answers with one of three results: allowed, blocked, or try again.

Automatic messages

Password resets, welcome messages and receipts must arrive once and say the right thing. For each message, store one record per event and create it before sending, so a job that runs twice finds the record and does nothing. And "delivered" does not mean "correct": we once sent an email whose whole body read "[object Object]" while the sending service reported success. After a new automatic message goes live, open a real copy and read it. Chapter 11 of the book also covers Google sign-in for employees and the full session story.

Key takeaways

Frequently asked questions

How do I disable signup in Supabase properly?

Turn off new signups in the authentication settings, not only on the screen, and remove the unused signup code. Keep login enabled for existing users if you need it.

Why should password attempt limits be on the server?

A limit inside the app only stops people using the app. Anyone can call the login service directly, so without a server-side limit they can guess passwords without restriction.

What is a refresh token?

It is a longer-lived ticket the app uses to get a new short-lived access token before the old one expires. It keeps users logged in without asking for the password again every hour.