# function authorization ([Authorize] on a function)

> `[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.

<!-- id: function-authorize · area: function · stability: stable · html: https://osysharp.com/reference/function/authorize/ -->

## Summary        {#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.

```osy syntax
policy IsStaff => RoleGrant.Any(g => g.Member == user && g.Role == AppRole.Staff);

[Authorize(IsStaff)]
void RefundOrder(Order order) { … }
```

## Signature      {#signature}
```osy syntax
[Authorize(PolicyName)]          // a declared, no-argument policy, written as a bare name
<ReturnType> Name(<params>) { … }
```

## Description    {#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)](https://osysharp.com/reference/ui/authorize/)) and a body's `if (IsStaff)` are — one
declaration, one meaning, wherever it is named.

## Examples       {#examples}
A staff-only action that touches no data — the shape nothing else protects:

```osy title="staff-only-action" test app=function-authorize
[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       {#see-also}
- [page authorization (policies)](https://osysharp.com/reference/ui/authorize/) — the same `[Authorize]` on a page.
- [policy](https://osysharp.com/reference/security/naming-a-policy/) — declaring a `policy`.
- [security { }](https://osysharp.com/reference/security/entity-security/) — the data rules, which guard every row a function touches.
