Signature#
Session.CurrentUser // your [Principal] entity — the signed-in person. Null when nobody is.
Session.CurrentUser.Email // any SCALAR of theirs — resolved in place, no round trip
Session.SignOut() // the other end of the session — drops the ticket, back to the login pageAvailable in every component, with no using. Requires the app to declare a [Principal] entity.
Summary#
Session.CurrentUser is the signed-in principal — a row of whatever entity your app marked [Principal], with the
fields you declared on it. It is null when nobody is signed in, which is the honest answer and the reason a page
that needs a user should say so with routing rather than by checking here.
You can read it in any of the three places a component holds a value, and you do not have to know which:
- as a member —
var me = Session.CurrentUser;— the whole row, fetched when the page loads; - in a render expression —
Text(Session.CurrentUser.Email); - inside an action —
new Order { Reference = reference, Owner = Session.CurrentUser };
The third one is the one that used to need a workaround. It does not any more: the compiler hoists the read onto the component and the action closes over it, which is precisely what you would have written by hand.
Description#
Its type: your one kind of account#
On the kits, the [Principal] is the Identity kit's base Principal, and your app signs people in as a type that
derives from it — User from the UserAccounts kit, or your own entity Member : Principal. When the app has exactly
one concrete type derived from its [Principal], Session.CurrentUser IS that type, so its own fields are there
with no cast:
var me = Session.CurrentUser; // a User, when User is the app's one kind of account
Text(me?.SecondFactorOnAt is null ? "Turn on two-step sign-in" : "Two-step sign-in is on");With two or more (User and Employee), it is the base Principal, and Session.CurrentUser is Employee tells them
apart. user in a security rule is always the base: a rule describes every kind of account, including one a later
version adds.
Reading a single field costs nothing#
A scalar of the principal — Session.CurrentUser.Email, .Id, or any column you declared — resolves in place, on
whichever side is asking, with no round trip. The browser reads it from the bag the server sent at boot, built
server-side, so a field your security rules mask reads as it does on the server — null, or a value type's default
(Security.IsMasked — was this value withheld from me?) — in the browser too. Use it
freely in a render expression, a filter, or a field default:
Text($"Signed in as {Session.CurrentUser.Email}");
live var mine = Order.Where(o => o.Owner.Id == Session.CurrentUser.Id);Reaching through a reference (Session.CurrentUser.Manager.Name) is a different question — the browser holds the
manager as an id, not as a row — so that stays a server read and belongs on a member.
The whole row, in an action#
An action runs in the browser, and the browser cannot run a database read in the middle of one. So when an action
mentions Session.CurrentUser, the compiler lifts the read onto the component as an ordinary fetched member and the
action reads that. Several actions on one page share one fetch.
[Principal] entity User {
[Required, MaxLength(200)] string Email;
security { allow read when IsAuthenticated; }
}
entity Order {
[Required, MaxLength(50)] string Reference;
User Owner;
security { allow read, create when IsAuthenticated; }
}
[Route("/orders/new")]
[Render(CSR)]
component NewOrder() {
string reference = "";
action Save() {
new Order { Reference = reference, Owner = Session.CurrentUser };
UnitOfWork.Commit();
}
render {
Stack {
Input(value: reference, placeholder: "Reference");
Pressable(onClick: Save) { Text("Save"); }
}
}
}⚠ Other reads behave differently inside an action, and it is worth knowing which. An ordinary read —
Order.Where(…) — is not yet available inside an action: bind it to a member and read the member instead. The
browser has no database, so a read in the middle of an action means a round trip at that moment; the compiler does not
yet arrange one. Session.CurrentUser needs no such arrangement — its value is fixed for as long as the page is open
(signing in or out reloads the app), so it is fetched with the page and simply read.
It is who is being SHOWN the page, not who wrote the row#
A [CreatedBy] field on an entity ([CreatedAt] [CreatedBy] [ModifiedAt] [ModifiedBy]) is stamped by the server from the request's principal.
Your own field (Owner above) is the app's view; the stamp is the platform's, and no app code can write it. When you
want proof of who did something, declare the stamp and read it. When you want a relationship you control —
who a task is assigned to, whose basket this is — declare your own reference and set it.
Nobody is signed in#
Session.CurrentUser is null for an anonymous visitor, and asking whether it is null costs nothing — the comparison
is answered by the principal's id, which every side already holds:
[Principal] entity User {
[Required, MaxLength(200)] string Email;
security { allow read when IsAuthenticated; }
}
[Route("/")]
[AllowAnonymous]
[Render(CSR)]
component Home() {
render {
Stack {
Text(Session.CurrentUser == null ? "Join us" : "Welcome back");
if (Session.CurrentUser != null) { Text($"Signed in as {Session.CurrentUser.Email}"); }
}
}
}That is the right tool for choosing what to SHOW. Do not use that null as a gate: a page that requires a user
should require one at the route (routed components are protected unless they say [AllowAnonymous]), so the page is
never rendered for someone who is not there. If you want a name for work done before sign-in, that is
Visitor.
See also#
- Visitor — the anonymous twin, for work that begins before there is a user
- component — members, actions, and where each kind of value lives
- [[security-auth-bootstrap#sign-out]] —
Session.SignOut(), the verb that ENDS the session this page reads - principal predicates (IsAuthenticated / IsAnonymous / IsTheApp) and open reads — the predicates that read the same principal in a
security { }block