Skip to content
SynthoCore Automation Inc.
  • Home
  • SynthoDesk
  • SynthoFlow
  • SynthoOps
  • Company
  • Engineering
  • Pricing
  • Contact
Sign in

Engineering

Fail-closed by default: designing a sign-in door that refuses politely

Published August 30, 2026 · SynthoCore Automation Inc.

A sign-in door has two ways to be wrong. It can let in someone it should have stopped, or it can stop someone it should have let in. These are not symmetric mistakes. The first hands access to a person who was never entitled to it; the second inconveniences a person who can email support and be sorted out in a minute. When a door is unsure — a value it needed was missing, a dependency did not answer, a rule was written but never finished — it has to fall one way or the other. A door that fails closed falls toward the survivable mistake, and that single bias, applied everywhere, is most of what separates a door you can reason about from one you can only hope about.

This is the engineering story behind the door at login.synthocore.ca — not the wiring, but the design decisions any practitioner faces. The standards under it are ordinary: an OpenID Connect authorization-code flow hardened with PKCE to sign a user in, and RFC 7662 token introspection to ask, at the moment of access, whether the token in hand is still good. What matters is not which standards you pick, but what the system does where the answer is not a clean yes.

Deny if absent beats permit by default

The first decision is what a missing answer means. A permissive default reads an absent rule as allow: if nothing said to stop this request, let it through. A fail-closed default reads the same absence as deny: if nothing affirmatively granted this request, refuse it. Both are one line of code, and they diverge completely under the conditions that actually cause incidents — a config that did not load, a flag half-migrated, a rule someone meant to add and did not.

Under a permit-by-default door, every one of those gaps opens quietly, and you find out when someone reaches a thing they should not have. Under a deny-if-absent door, the same gaps close just as quietly, and the failure mode is a legitimate user filing a ticket and getting fixed. You want your unknowns to land where the worst outcome is a support email. Deny-if-absent puts them there by construction: the door grants only what some rule said, in words, to grant, and treats everything it was never told about as not granted.

An empty allowlist refuses everyone — on purpose

The cleanest test of whether a door is really fail-closed is to empty out the allowlist — the set of who is permitted — and see what happens. A correctly built door now refuses everyone, including the people who built it. That feels alarming the first time, and it is exactly right: an empty allowlist is the honest representation of “no one has been granted access yet.” If an empty list somehow lets people in, access was never really coming from the list — it was coming from a default hiding underneath, and you have a permit-by-default door wearing an allowlist as decoration.

The property to hold onto is that access is additive from zero: you start at no one, and every entry is a deliberate addition you could point to and defend. A door that starts from “everyone” and subtracts exceptions makes forgetting dangerous — the exception you fail to write is an opening. A door that starts from “no one” and adds grants makes forgetting merely inconvenient — the grant you fail to write is a locked-out user. Same human error, opposite blast radius.

Signed in is not the same as entitled

The most common conflation in sign-in design is treating authentication and authorization as one gate. They are two questions, asked in order. Authentication answers who are you, and its failure is honest confusion: the credentials did not check out, the token is not valid. Authorization answers are you allowed here, and its failure is a decision: you are exactly who you say you are, we know precisely who that is, and the answer is still no.

HTTP gives these two different numbers, and the difference is not pedantry. A refusal to authenticate is a 401 — we could not establish who you are. A refusal to authorize is a 403 — we know who you are and you are not entitled to this. Collapsing the second into the first sends an entitled-looking user to re-enter credentials that were never the problem, and it buries an event worth recording: a fully authenticated principal being turned away, filed under “auth failure,” vanishes into the noise of mistyped passwords. A 403 is not an error in the sign-in. It is the sign-in working, and then the entitlement check doing its separate job and returning no.

“We couldn’t sign you in” and “we can’t answer” are different pages

A second boundary matters as much as the first: the one between a request the door refused and a request the door could not process. A 4xx means you — something about this request cannot be honored: you are not signed in, not entitled, not allowed here. A 5xx means us — the door itself is impaired, a dependency did not answer, and it cannot render a verdict at all. These deserve different pages because they ask different things of the reader.

The 4xx page is a closed door with a clear sign: you were not let in, here is who to contact, nothing is broken. The 5xx page is a service admitting it is temporarily unable to decide; the honest message is try again shortly, not “you are not allowed,” because being unable to answer is not the same as answering no. Show a 5xx dressed as a 4xx and you have told a user they lack access they may well have; show a 4xx dressed as a 5xx and you have taught them to retry their way past a refusal. And fail-closed still governs the 5xx case: when the door cannot decide, it does not open — it shows the “we can’t answer” page and grants nothing. Unsure resolves to denied, never to allowed.

Quiet pages, loud logs

The last decision is where the truth goes. An error page shown to whoever is standing at the door should be deliberately incurious: it should not disclose which check failed, which service was down, whether an account exists, or how far into the flow the request travelled. Every specific cause you print onto a public page is a fact handed to whoever is knocking — and some of them are knocking to learn exactly those facts. The public message states that something did not work and offers a next step. It does not narrate.

But the cause cannot simply vanish, because whoever fixes the door needs precisely what the page withholds. So the causes go the other way — into server-side logs, keyed by a request identifier the error page can safely show, where the operator sees which check failed, which dependency timed out, and where in the flow it stopped. The page and the log are two audiences with opposite needs: one should learn as little as possible, the other as much as possible. The shared identifier is the thread between them — the user quotes it to support, the operator finds the exact event, and no diagnostic detail ever had to reach the public page.

The through-line

Every one of these choices is the same instinct in a different place. When a rule is missing, deny. When the list is empty, refuse. When a user is authenticated but not entitled, say no plainly and separately. When the door cannot decide, stay shut and say so honestly. When something breaks, tell the visitor little and the log everything. It is a single decision — that the unsure answer is no — carried through every corner of the design, so the door’s behavior under failure is something you can state in advance rather than discover in production. A door built this way refuses politely, refuses safely, and refuses in a way its own operators can explain.

If this is the kind of rigour you want in how your systems are designed and reviewed, get in touch.

SynthoCore Automation Inc.

Engineering-automation software — SynthoDesk, SynthoFlow and SynthoOps.

Email us: info@synthocore.ca

Products

  • SynthoDesk
  • SynthoFlow
  • SynthoOps
  • Pricing

Company

  • About SynthoCore
  • Governed AI
  • FAQ
  • Engineering
  • Contact
  • Privacy Policy
  • Terms of Service
© 2026 SynthoCore Automation Inc. All rights reserved. Legal