Summary#
A public page often has one or two things only a signed-in visitor may do — Add to order, Message the
seller, Book. Put [Authorize] on the action that does it:
[Route("/wines")] [AllowAnonymous]
component Wines() {
[Authorize] action Add(Wine w) { AddToOrder(w); } // any signed-in caller
[Authorize(Sommeliers)] action Taste(Wine w) { BookTasting(w); } // a declared policy
render { … Button("Add to order", onPress: () => Add(w)); … }
}Everyone sees the catalogue. Only a signed-in visitor sees the Add to order button, and only someone who
satisfies Sommeliers sees Book a tasting. Everyone else sees nothing where those buttons sit — no disabled
button, no error.
Signature#
[Authorize] action Name(<params>) { … } // any signed-in caller
[Authorize(Policy)] action Name(<params>) { … } // a signed-in caller who satisfies the declared `policy`
[Authorize(Policy)] <Type> Name(<params>) { … } // the same on a component methodPolicy— a declared, no-argumentpolicy(see page authorization (policies)). It is checked like any other policy reference: a name that is not declared is a compile error. Because the page decides whether to show the button from the policy, it must be one a control can reflect — the same rule ascanSee:(canPress / canEdit / canSee).- Bare
[Authorize]means "anyone signed in".[Authorize(IsAuthenticated)]says the same thing out loud.
Description#
[Authorize] on an action does two things, and you need both:
- The page does not show it. Any control whose event runs the action —
onPress: Add,onPress: () => Add(w)— is rendered only for a caller who passes the gate. The smallest element that names the action is the one hidden, so aStackaround the button stays. Reading the action's.Pendingflag (a busy label) does not count as running it. - The server does not run it. Every server call the action makes — and its
UnitOfWork.Commit()— is refused for a caller who does not pass the gate, whatever the browser sends. A signed-out visitor is told to sign in; a signed-in caller without the policy is told their account does not satisfy it. Nothing in the action runs.
This is how the members-only component behaves too ([Authorize(Policy)] component … placed on a public page — see
page authorization (policies)); the attribute on the action saves moving the button and its action into a component of their own.
The functions the action calls keep their own access rules. [Authorize] on the action decides who may run the
action; a caller who skips the page and names a server function directly meets that function's own grant. So a
public page can leave AddToOrder gated, as every function is unless marked [AllowAnonymous], and the compiler no
longer refuses the page for calling it — the action that calls it is now a signed-in caller's action.
Who may call a gated action#
The server knows a click by the action the control ran. So an action that RUNS a gated one must carry the same
[Authorize] — otherwise the gated body would run under the ungated action's name — and this is a compile error. An
on mount hook, an on change block or a field cannot carry a gate at all, so they cannot run a gated action; a
gated action runs from a control. An inline handler (onPress: () => { Add(w); }) takes the gate of the action it
runs; one handler cannot run two actions with different gates.
Examples#
A public wine list. Anyone can browse; a signed-in buyer can add a wine to their order; a sommelier can book a
tasting. AddToOrder stays an ordinary, gated server function.
[Principal] entity Buyer {
[Unique, MaxLength(200)] string Email;
security { allow read when IsAuthenticated; }
}
[Role] enum Level { Buyer, Sommelier }
[RoleGrant] entity Grant {
[Required("Who the grant is for is required.")] Buyer Holder;
Level Level;
security { allow read when IsAuthenticated; }
}
policy Sommeliers => Grant.Any(g => g.Holder == user && g.Level == Level.Sommelier);
entity Wine {
[Required("A wine needs a name."), MaxLength(80)] string Name;
security { allow read when IsAuthenticated || IsAnonymous; }
}
entity OrderLine {
[Required("Which wine is required."), MaxLength(80)] string Wine;
security { allow read, create when IsAuthenticated; }
}
void AddToOrder(string wine) { new OrderLine { Wine = wine }; UnitOfWork.Commit(); }
[Route("/wines")]
[Title("Wines")]
[AllowAnonymous]
[Render(CSR)]
component Wines() {
live var wines = Wine.Take(50).ToList();
[Authorize] action Add(string name) { AddToOrder(name); }
[Authorize(Sommeliers)] action Taste(string name) { AddToOrder("tasting: " + name); }
render {
Stack {
foreach (var w in wines) {
Text(w.Name);
Button("Add to order", onPress: () => Add(w.Name)); // signed-in buyers only
Button("Book a tasting", onPress: () => Taste(w.Name)); // sommeliers only
}
}
}
}See also#
- page authorization (policies) — policies,
[Authorize(Policy)]on a page, and the members-only component - canPress / canEdit / canSee —
canSee:/canPress:: reflect a policy on a control without gating an action - routes and pages — protected-by-default pages and
[AllowAnonymous] - Session.CurrentUser — who is signed in, from a component