Who You Are, What You May Do, and Who Remembers
Three separate problems get solved by one library and called login. Separating them is the whole of this lesson, because every later decision depends on which one you are answering.
Three questions
A request arrives asking to delete a document. Three separate things have to
be established before it can be carried out, and they are usually described
with one word.
Who is making this request? That is identification. It binds the request to
an account, and when it fails the honest answer is that you do not know who
this is.
May that account delete that document? That is permission. It depends on the
account, on the specific document, and on the specific operation, and it is
a different answer for each combination.
How did we know the answer to the first question without asking the person
again? That is the session. Identity was proved once, minutes or weeks ago,
and something has been carrying that fact ever since.
| step | stage | the question | what it uses | when it fails | what happened |
|---|---|---|---|---|---|
| 1 | session | has identity been proved recently | a value the browser sent | no identity, ask them to sign in | This stage knows nothing about documents. It produces an account identifier or nothing at all. |
| 2 | identity | which account is this | the record the session points at | the account was deleted or disabled | Worth separating from the stage above, because a valid session can point at an account that no longer exists. |
| 3 | permission | may this account delete this document | the account, the document, the operation | known, but not allowed | This is the only stage that looks at what is being asked. It is also the only one that needs to load the document. |
| 4 | the work | delete it | the document | it was already deleted | The actual operation, which assumes the first three succeeded and should never check them again. |
Identifying somebody
Identification binds this request to an account. That is the whole job, and
it is worth being strict about how little it establishes.
It does not say the account is in good standing. It does not say the person
is who the account says they are, only that whoever is here presented
something the account controls. It does not say they may do anything at all;
an account with no permissions is perfectly well identified.
The usual way to be strict about this is to write it as a function that
returns an account identifier or nothing, and takes no argument describing
what is being attempted. If the function needs to know what is being asked,
two questions have been merged.
| answers who you are | answers what you may do | remembers the answer | worth stealing | |
|---|---|---|---|---|
| a password check | 1 | 0 | 0 | 1 |
| a one-time code by email | 1 | 0 | 0 | 1 |
| a session cookie | 0 | 0 | 1 | 1 |
| a role on an account | 0 | 1 | 0 | 0 |
| a rule about a document | 0 | 1 | 0 | 0 |
| a signed token carrying | 1 | 0 | 1 | 1 |
| an entry in a session st | 0 | 0 | 1 | 0 |
Deciding what they may do
Permission takes three inputs: the account, the thing, and the operation.
All three are necessary, and designs that drop one are the usual source of
trouble.
Dropping the thing gives you a role check and nothing more: this account is
an editor, so it may edit. That works until two editors must not edit each
other's documents, at which point every rule has to be rewritten.
Dropping the operation gives you access as a single bit: this account can
touch this document. That collapses reading and deleting into one answer,
which is almost never what anybody wants.
The second useful discipline is to check permission as late as possible,
against current data. A permission established at sign-in and carried
forward is a statement about the past. Somebody removed from a project at
ten o'clock should not be able to act on it at ten past, and whether that
holds depends entirely on whether the check reads the current membership or
a copy made at sign-in.
Remembering the answer
Proving identity properly is slow. It involves a person, a password manager,
sometimes a phone, sometimes a second device. Doing it once per request is
not a performance problem, it is an impossibility.
- identity checks per day if nothing is remembered
- active users in a day
- requests each user makes
So the answer is remembered: something is issued at sign-in, the browser
carries it, and every subsequent request presents it instead of a password.
That something is now equivalent to the password for as long as it lives,
which makes it the most valuable thing in the system and the subject of the
next several lessons.
Two consequences are worth stating now. The thing being carried has to be
unguessable, because anybody holding it is you. And it has to have a
lifetime, because an answer to the question of who you are gets staler every
hour, and at some point the honest thing to do is ask again.
Where the three get merged
Most confused designs are one question being answered with another one's
tool, and three merges account for nearly all of them.
The first merge treats a valid session as permission to act. Code reaches a
handler, observes that somebody is signed in, and proceeds. It is usually
correct, because most signed-in people are acting on their own data, and it
fails precisely when somebody passes an identifier belonging to somebody
else.
The second bakes permissions into a token at sign-in. It is appealing
because it removes a lookup. It means a permission change takes effect
whenever the token happens to expire, which is a sentence nobody wants to
say in an incident review.
The third gives the same answer to both refusals, so a user who is not
allowed to see a page is asked to sign in, signs in successfully, and is
asked again. They conclude that sign-in is broken, which is a reasonable
conclusion from what they can see.
The fourth trusts an external identity provider for a decision only you can
make. A provider can tell you which account this is. It cannot tell you
whether that account may delete this document, because it has never heard of
the document. The fifth lesson returns to this once the exchange itself is
clear.
The next lesson goes back to the beginning of the chain and asks how
identity gets proved in the first place, and what each method actually
resists.
Recap
- Identifying somebody, deciding what they may do, and remembering the answer are three problems with three different failure modes.
- A session proves the first question was answered once; it never answers the second, and treating it as though it does is the most common design error here.
- Point at the exact line in your own request path where each question is answered, and you will usually find one of them is not answered anywhere.
This is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents