Summary#
Mark a function [Authorize(PolicyName)] and only a caller who satisfies that policy can run it. Anyone else
gets a NotAuthorized exception before the function's first line runs.
policy IsStaff => RoleGrant.Any(g => g.Member == user && g.Role == AppRole.Staff);
[Authorize(IsStaff)]
void RefundOrder(Order order) { … }Signature#
[Authorize(PolicyName)] // a declared, no-argument policy, written as a bare name
<ReturnType> Name(<params>) { … }Description#
Why a function needs it. Your entities' security { } rules guard every row a function reads or writes — so a
function that only moves data is already protected by them. What they cannot see is what a function does outside
your data: a refund through a payment provider, an email, a call to another system. Those actions touch no row, so no
data rule is ever asked. [Authorize] is the gate for them.
- Checked everywhere the function is entered. From a page action, an API route or a test — and when another
function calls it. An
[AllowAnonymous]function cannot reach a staff-only one by calling it. - An ordinary exception. A refused call throws
NotAuthorizedat the call site, before the function's first statement. A caller's owntry/catchsees it, and so doesAssert.Throws<NotAuthorized>(…)in a test. - Fail closed. A policy that cannot be evaluated refuses the call. It never quietly answers "yes".
- Compile-checked. The name must be a declared policy with no parameters — at a function's entry there is
nothing to bind a parameter to. For a policy that takes an argument, check it in the body where the argument is
known:
if (!CanManage(team)) { throw new NotAuthorized("…"); }. - Not beside
[AllowAnonymous]or[AuthMethod]. Both admit a caller who is signed in as nobody, and a policy is a question about who the caller is — the pair is refused as a contradiction.
The policy is evaluated exactly as a page's [Authorize] (page authorization (policies)) and a body's if (IsStaff) are — one
declaration, one meaning, wherever it is named.
Examples#
A staff-only action that touches no data — the shape nothing else protects:
[Role] enum AppRole { Staff, Member }
[Principal] entity Person {
[Required, MaxLength(60), Unique] string Email;
security { allow read when IsAuthenticated; }
}
[RoleGrant] entity RoleGrant {
[Required] Person Grantee;
AppRole Role = AppRole.Member;
security { allow read when IsAuthenticated; }
}
policy IsStaff => RoleGrant.Any(g => g.Grantee == user && g.Role == AppRole.Staff);
/// Sends money back through the payment provider — no row changes here, so no `security { }` rule would ever see it.
[Authorize(IsStaff)]
string IssueRefund(string paymentId) {
return "refund requested for " + paymentId;
}See also#
- page authorization (policies) — the same
[Authorize]on a page. - policy — declaring a
policy. - security { } — the data rules, which guard every row a function touches.