Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

Session.CurrentUser

Session.CurrentUser — the signed-in principal, readable anywhere in a component

`Session.CurrentUser` is the person the app is being shown to, as your own `[Principal]` entity. Read it in a member, in a render expression, or straight inside an action — the compiler puts the read where it belongs, so `Owner = Session.CurrentUser` in a Save button means what it looks like it means. A single scalar of it (`Session.CurrentUser.Email`, `.Id`) costs no round trip at all. It is null for a visitor who has not signed in.

stable2 examples compiled by CIuisessionidentityprincipal

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 page

Available 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#

Related

Visitor

`Visitor.Id` names the browser someone is using. The platform issues it on their first visit, proves it on every…

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

auth bootstrap (login, before anyone is signed in)

Under deny-all, login faces a paradox: it must read a user row *before* anyone is authenticated. `app.AuthBootstrap`…

principal predicates (IsAuthenticated / IsAnonymous / IsTheApp) and open reads

Two built-in `when` predicates say who a request is: `IsAuthenticated` is a signed-in user, `IsAnonymous` is an…