Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

function authorization ([Authorize] on a function)

[Authorize(PolicyName)] <ReturnType> Name(<params>) { … }

`[Authorize(Policy)]` on a function runs it only for a caller who satisfies the named `policy` — checked when it is called from a page, an API or a test, AND when another function calls it. It is the gate for what a function does OUTSIDE your data (a refund, an email, a call to another system), which no `security { }` rule can see.

stable1 example compiled by CIfunctionsecurityauthorization

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 NotAuthorized at the call site, before the function's first statement. A caller's own try/catch sees it, and so does Assert.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#

Related

page authorization (policies)

A `policy` names a reusable authorization predicate over the current user — e.g. `policy Admins => UserRole.Any(r =>…

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…

policy

Names an authorization rule once so every place that needs it can say the name. A policy is a boolean about the caller…