The auth provider, end to end
You have the split, the ladder, the return values and the exception. What is missing is the thing that makes any of it real: a genuine identity provider on the front of it, and a walk of the whole path from a request arriving to a scoped result coming back.
One scoping note first. The specific provider, whether that is a hosted authentication service, your company’s single sign-on, or something you run yourself, is illustrative. Provider-specific configuration is a lookup and it changes on their schedule, not yours. What is examinable, and what survives changing providers, is the shape of the handler and where each decision belongs.
What sits behind authenticate
In development your authenticate handler probably compares a static key and moves on. In production it validates a bearer token issued by a real provider, and the work splits in two.
Validation confirms the token genuinely came from the issuer you trust, is not expired, and has not been tampered with. That is cryptographic work you delegate to the provider or to a library. Hand-rolling it is how you end up accepting tokens you should not.
Identity construction turns the validated token into the object every downstream decision reasons about: the caller’s identifier, plus the attributes your policy needs, such as organization membership, tenant, or asserted permissions.
Where each rule belongs
The temptation, once the token contains an organization id, is to enforce with it immediately. Resist that, and the reason is structural rather than stylistic: authenticate does not know which resource is being touched. It runs once, before the request has been resolved to a thread or a store namespace or a run. It cannot make a per-resource decision because it does not have a resource.
So tenant scoping belongs in an authorization handler, expressed as a metadata filter derived from the identity. That is precisely what the dict return value exists for: say it once, centrally, and every listing and read is confined without any endpoint doing filtering by hand.
Except for the store, where the same intent is expressed by rewriting the namespace, because the store has no metadata to filter. That exception is not an inconsistency, it is the same idea applied through the structure the store actually has.
The failure direction
Providers become unreachable. When validation cannot complete, the handler fails the authentication. An identity that cannot be verified is not an identity, and there is no safe middle ground available: returning an empty filter is a scoping instruction, and scoping instructions presuppose an authenticated caller you do not have.
This is worth stating plainly because the wrong answer is genuinely attractive under production pressure. Availability arguments are how systems end up failing open, and an auth system that fails open under load is failing open exactly when it is most likely to be under attack.
The whole line, one pass
A request arrives with a credential. authenticate validates it and returns an identity. The most specific registered @auth.on handler for that resource and action runs, and returns None or True to allow, False to deny with a 403, or a dict as a metadata filter. If the resource is the store, scoping happens through a namespace rewrite instead. Only then does the actual work happen, already confined to the caller’s slice.
Every station on that line is a fact from this module. If you can say the line out loud without stopping, this focus area is done.
Try it yourself
What the authenticate handler does with a token
The provider is illustrative and interchangeable. The shape of the handler is not.
A bearer token arrives from a real identity provider. Describe the shape of what your authenticate handler has to do with it.
Reveal answer
Validate it, then turn it into an identity. Validation means confirming the token genuinely came from the provider you trust and has not expired or been tampered with, which is work you delegate to the provider or to a library rather than hand-rolling. Turning it into an identity means returning the caller's identifier along with whatever attributes the authorization handlers will need, such as tenant or organization membership and any permissions the provider asserts. What you return is the object every downstream authorization decision reasons about, so a claim you fail to carry through is a claim your policy cannot use.
Where tenant scoping belongs
Your provider issues tokens carrying an organization id. Users must only ever see their own organization's threads. Where does that rule get expressed?
Show answer
Correct answer: C — In an authorization handler, by returning a metadata filter derived from the identity
A metadata filter derived from the established identity is exactly what the dict return value exists for: it scopes the resources a request operates over, expressed once, centrally. The tempting wrong answer is the first, because the organization id is right there in the token and rejecting early feels defensive and tidy: authenticate has no idea which resource is being touched, so it cannot make a per-resource decision, and pushing access logic into it produces a policy that is simultaneously too blunt and in the wrong place. Filtering per endpoint is the thing the platform is offering to stop you doing.
Which failure mode is correct
Your identity provider becomes unreachable and token validation cannot complete. What must the handler do?
Show answer
Correct answer: B — Fail the authentication, because an identity that cannot be verified is not an identity
An identity that cannot be verified is not an identity, so the honest outcome is a failed authentication rather than a guess. The tempting wrong answer is the empty metadata filter, because it sounds like a conservative compromise and the vocabulary is drawn from this very module: an empty filter is a scoping instruction, which presupposes an authenticated caller, so returning one from a failed authentication is asserting an identity you do not have. Falling back to allow is the classic wrong-side failure, and skipping to authorization hands a decision to handlers whose entire input is the identity that was never established.
The request path, end to end
Walk the whole thing once. Everything in this module is a station on the same line.
Trace a request from arrival to a scoped result, naming what happens at each stage.
Reveal answer
The request arrives at the server carrying a credential. The authenticate handler validates it against the identity provider and returns an identity with its attributes. The authorization handlers are consulted, and the most specific registered handler for that resource and action is the one that runs. It returns one of four things: None or True to allow, False to deny with a 403, or a dict acting as a metadata filter that narrows which resources the request operates over. If the resource is the store, the scoping happens by rewriting the namespace instead. Only then does the run, the thread read, or the store access actually take place, already confined to the caller's slice.
Write the policy on paper
No provider account, no server. This is a policy design exercise and paper is the correct medium for it.
- Take a two-tenant product and write down, in prose, the complete access policy for threads, runs and the store.
- For each rule, mark which handler level it belongs at, and justify why it is not at a broader or narrower level.
- For each rule, write which of the four return values expresses it.
- Now find the rule that cannot be expressed as a metadata filter and say what happens to it instead.
- Finally, write down what your policy does when the identity provider is unreachable, and check that answer against the fail-safe direction.