# page authorization (policies)

> A `policy` names a reusable authorization predicate over the current user — e.g. `policy Admins => UserRole.Any(r => r.User == user && r.Role == Role.Admin)`. A page marks itself `[Authorize(Admins)]` to require it: an authenticated user who does not satisfy the predicate is refused the page. The reference is compile-checked — `[Authorize(Typo)]` is a build error, not a silent hole.

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

## Summary        {#summary}
A **`policy`** names a reusable **authorization predicate** — a condition over the current user (`user`) that says
who is allowed. A page requires a policy with **`[Authorize(Name)]`**; a logged-in user who does not satisfy the
predicate is refused the page (the server returns it to no one who fails the check).

```osy syntax
policy Admins => UserRole.Any(r => r.User == user && r.Role == Role.Admin);

[Route("/admin")]
[Authorize(Admins)]
component AdminPanel() { render { Text("secret"); } }
```

The reference is a **checked symbol, not a string**: `[Authorize(Admins)]` names the declared `policy Admins`, so a
typo (`[Authorize(Admin)]`) is a **compile error**. A policy is declared once and reused across as many pages as need
it.

## Signature      {#signature}
```osy syntax
policy Name => <predicate over `user`>;   // a reusable named authorization predicate
[Authorize(Name)] component Page() { … }  // require it — the principal must satisfy Name
```

The predicate is ordinary Osy#: it reads `user` (the current principal) and queries your data. A role check is the
common shape — `RoleEntity.Any(r => r.User == user && r.Role == Role.X)` — but any boolean predicate over the user
works.

## Description    {#description}
Pages are **protected by default** (see [routes and pages](https://osysharp.com/reference/ui/routing/)): a routed `component` requires an authenticated principal
unless it is `[AllowAnonymous]`. `[Authorize(Name)]` narrows that further — the authenticated principal must also
**satisfy the named policy**. If they don't, the page is refused.

- **`policy Name => <predicate>;`** — declares the predicate once, by name. It reads `user` and may query entities
  (typically a role-grant table). The same policy can gate many pages.
- **`[Authorize(Name)]`** — attaches the policy to a page. The reference is compile-checked against the declared
  policies; an unknown name fails the build.
- **Fail closed** — a policy that cannot be resolved or evaluated refuses the page. If the named policy is missing or
  its predicate errors, the page is refused, never served — authorization never falls open.

Authorization is enforced on the **server**, when the page's definition is requested — so it holds regardless of
what the client does.

### A members-only part of a public page   {#component}
`[Authorize(Name)]` works on a component a page RENDERS, not only on a page. Put it on the part of a public page that
is for some visitors only — an "Enrol" button for members — and the public page may place it: for a visitor, or for a
signed-in user who does not satisfy the policy, nothing is there; for one who does, it renders and runs. The server
never sends the component to anyone who does not satisfy the policy, and the functions it calls keep their own
`[Authorize]`, checked when they are called.

```osy title="a public timetable with an Enrol button for members" test app=ui-authorize-part
[Principal] entity Member { string Email; }
[Role] enum Level { Member, Coach }
[RoleGrant] entity Grant { [Required] Member Holder; [Required] Level Level; }

policy Members => Grant.Any(g => g.Holder == user && g.Level == Level.Member);

entity Enrolment { [Required, MaxLength(50)] string Course; }

[Authorize(Members)]
void Enrol(string course) { new Enrolment { Course = course }; UnitOfWork.Commit(); }

[Authorize(Members)]
component EnrolButton(string course) {
  action DoEnrol() { Enrol(course); }
  render { Button("Enrol in " + course, onPress: DoEnrol); }
}

[Route("/classes")]
[AllowAnonymous]
[Render(CSR)]
component Classes() {
  render {
    Stack {
      Text("Yoga, Tuesday 18:00");
      EnrolButton("yoga");   // members see the button; everyone else sees the timetable
    }
  }
}
```

A bare `[Authorize]` names no policy and is no boundary — every component already requires a signed-in caller — so
a public page still cannot place one; name the policy (`policy SignedIn => IsAuthenticated;` for "anyone signed in").

When the members-only part is a single button, you do not need a component for it: put `[Authorize]` (or
`[Authorize(Policy)]`) on the **action** the button runs, and the public page hides the button from anyone who does
not pass and the server refuses the action for them — see [[Authorize] on an action](https://osysharp.com/reference/ui/authorize-action/).

## Examples       {#examples}
A role-gated admin page — a reusable `Admins` policy plus a page that requires it:

```osy title="admin-page" test app=ui-authorize
[Principal] entity User { string Email; }
[Role] enum Role { Admin, Viewer }
[RoleGrant] entity UserRole { [Required] User User; [Required] Role Role; }

policy Admins => UserRole.Any(r => r.User == user && r.Role == Role.Admin);

[Route("/admin")]
[Render(CSR)]
[Authorize(Admins)]
component AdminPanel() {
  render { Text("Admin only"); }
}
```

## See also       {#see-also}
- [routes and pages](https://osysharp.com/reference/ui/routing/) — protected-by-default routing and `[AllowAnonymous]`
- [component](https://osysharp.com/reference/ui/component/) — the page a policy gates
- [creating & saving data](https://osysharp.com/reference/ui/data-mutation/) — a create obeys the entity's write permissions, a related access check
- [[Authorize] on an action](https://osysharp.com/reference/ui/authorize-action/) — `[Authorize]` on one action of a public page, for a single members-only button
