Summary
SessionToken already carries a roles vector, but it is only consulted inside a
dispatch — SigningAuthorizer verifies the token and decides whether the call is allowed.
There is no way to ask, outside a dispatch, "who is signed in and what may they do?"
Why it matters
UI code needs that answer to shape itself, not just to be refused afterwards. A button
the current user is not permitted to use should be hidden or disabled, not enabled and
then rejected when clicked. Today the only way to know is to attempt the action and catch
the failure, which is the wrong interaction.
Applications therefore end up maintaining a second, parallel notion of "the signed-in
user and their permissions" — read by the UI — beside the one morph verifies on the
server. Two sources for the same fact, and no guarantee they agree.
Sketch
Something readable outside a dispatch, refreshed on login:
namespace morph::session {
/// The verified identity of the signed-in user, readable outside a dispatch.
struct Principal {
std::string id; // verified, not client-asserted
std::vector<std::string> roles;
std::unordered_map<std::string, std::string> claims;
[[nodiscard]] bool hasRole(std::string_view r) const;
};
/// Set once the token is verified; cleared on sign-out.
void setPrincipal(Principal);
[[nodiscard]] Principal const& currentPrincipal();
} // namespace morph::session
so a view can do:
deleteButton.setEnabled(morph::session::currentPrincipal().hasRole("editor"));
against the same source the server authorizes with.
Open questions
- Where does it live? A process-wide value is convenient for a desktop client but
wrong for a server handling many connections concurrently — probably per-connection
there, which suggests this belongs on a session object rather than in a global.
- Trust. It must be populated only from a verified token, never from what the client
claimed, or it becomes a client-controlled permission set.
- Staleness. Roles can change server-side mid-session; the API should make clear that
this is a cache and the server remains the authority.
- Relationship to
Context. The per-call session::Context already travels with each
dispatch; this would be the longer-lived counterpart, not a replacement.
Raising it as a design question rather than a concrete proposal — the scoping decision
seems like the part that needs agreement first.
Summary
SessionTokenalready carries arolesvector, but it is only consulted inside adispatch —
SigningAuthorizerverifies the token and decides whether the call is allowed.There is no way to ask, outside a dispatch, "who is signed in and what may they do?"
Why it matters
UI code needs that answer to shape itself, not just to be refused afterwards. A button
the current user is not permitted to use should be hidden or disabled, not enabled and
then rejected when clicked. Today the only way to know is to attempt the action and catch
the failure, which is the wrong interaction.
Applications therefore end up maintaining a second, parallel notion of "the signed-in
user and their permissions" — read by the UI — beside the one morph verifies on the
server. Two sources for the same fact, and no guarantee they agree.
Sketch
Something readable outside a dispatch, refreshed on login:
so a view can do:
deleteButton.setEnabled(morph::session::currentPrincipal().hasRole("editor"));against the same source the server authorizes with.
Open questions
wrong for a server handling many connections concurrently — probably per-connection
there, which suggests this belongs on a session object rather than in a global.
claimed, or it becomes a client-controlled permission set.
this is a cache and the server remains the authority.
Context. The per-callsession::Contextalready travels with eachdispatch; this would be the longer-lived counterpart, not a replacement.
Raising it as a design question rather than a concrete proposal — the scoping decision
seems like the part that needs agreement first.