Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

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.

stable1 example 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

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 membervar me = Session.CurrentUser; — the whole row, fetched when the page loads;
  • in a render expressionText(Session.CurrentUser.Email);
  • inside an actionnew 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#

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 null in the browser exactly as it does on the server. 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; }
}

[Page("/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#

Every entity is audited automatically, and CreatedBy on a saved row is stamped by the server from the request's principal. Your own field (Owner above) is the app's view; the audit column is the platform's, and no app code can write it. When you want proof of who did something, read the audit column. 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. 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` is a stable opaque id for the browser someone is using, minted on their first visit and remembered…

component

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

principal predicates (IsAuthenticated / IsAnonymous) and open reads

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