# principal predicates (IsAuthenticated / IsAnonymous / IsTheApp) and open reads

> Two built-in `when` predicates say who a request is: `IsAuthenticated` is a signed-in user, `IsAnonymous` is an unauthenticated visitor, and `IsAuthenticated || IsAnonymous` is everyone — said out loud. They are the sanctioned way to open a surface deliberately, because every `allow` must say WHO: a rule with no `when` and no `where` is a compile error, on all four verbs and in every app. An unqualified grant is not a weaker rule — it is the absence of one wearing the syntax of one. A third, `IsTheApp`, is true only when the app itself is acting — an `[On]` handler, a workflow's body — and false for every request, so code can tell the app's own work from a visitor's.

<!-- id: security-principal-predicates · area: security · stability: stable · html: https://osysharp.com/reference/security/principal-predicates/ -->

## Summary        {#summary}
Two **built-in `when` predicates** describe the request's principal, for use in an entity `security { }` block:

- **`IsAuthenticated`** — a signed-in user is making the request.
- **`IsAnonymous`** — no one is signed in (the unauthenticated visitor).
- **`IsAuthenticated || IsAnonymous`** — everyone, stated explicitly.

They exist because a bare **unqualified `allow`** — any verb, with no `when` guard and no `where` row filter — is a
**compile error**. It hands that verb to every caller, anonymous included, and the runtime consults nothing: on a read
it is the shape by which a `[Principal]` leaks its own credential columns, and on a `delete` it is worse. So you must
**say who**: a role, a `where` filter, or one of these predicates.

```osy syntax
[Principal] entity User {
  string Email;
  security {
    allow read when IsAuthenticated;   // any signed-in user may read the directory (not the world)
  }
}
```

## Signature      {#signature}
```osy syntax
allow read when IsAuthenticated;                 // a signed-in user
allow read when IsAnonymous;                      // an unauthenticated visitor
allow read when IsAuthenticated || IsAnonymous;   // everyone, explicitly
allow read when IsAuthenticated || IsStaff;       // compose with your own policies
```

Usable in a `when` guard or a `policy` body (a per-request fact about the principal — not a `where` row filter), and
usable **whether or not the app declares a `[Principal]`**: an app with none has exactly one kind of caller, so
`IsAnonymous` is true there and `IsAuthenticated` is false. `IsAuthenticated`, `IsAnonymous` and `IsTheApp` are
**reserved** — a `policy` may not take those names. The third is not about a caller at all: see
[the app itself is acting](#is-the-app).

## Description    {#description}
The rule applies to **every app and all four verbs**, whether or not a `[Principal]` is declared. It once fired only
for `read`, and only on apps that had a principal; both exemptions are gone. The principal exemption in particular was
a trap rather than a kindness — a principal-less app was let off, and then silently inherited every unqualified grant
the day it declared a `[Principal]`, having never been asked. These two predicates are **always bound**, so an app with
no principal can still say exactly what it means (`when IsAnonymous`).

`deny` has its own rule rather than an exemption: an entity-level `deny` with no `when`/`where` is refused too — under deny-all it subtracts from nothing, and a conditioned grant beside it is more specific and overwrites it ([[security-entity-security#layers]]). A FIELD-scoped one (`deny read Secret;`) stays legal: a field's blanket is more specific than every entity rule, so it is the only way to say "nobody, ever" about one column. ⚠ A CREDENTIAL column is the one exception, and it is refused for a reason worth knowing: masking it from everyone masks it from your own sign-in too, so `Security.VerifyPassword` compares against null and a correct password is refused exactly like a wrong one. Condition that one on your auth policy — `deny read PasswordHash when !IsAuthenticator;`.

The predicates read as intent:

- **`IsAuthenticated`** grants the read to anyone signed in, regardless of role — the common "members can see the
  directory, the public cannot" case, without inventing a role for it.
- **`IsAnonymous`** grants it to the not-signed-in visitor — a genuinely public page or catalog. (It is the identity
  of the request, distinct from any role a user account might hold.)
- **`IsAuthenticated || IsAnonymous`** is the honest way to write "everyone": broad, but deliberate, and greppable —
  a reader sees that the openness was chosen, not forgotten.

Prefer the narrowest that is true. Reach for the `||` form only when a read really is public; reach for a role or a
`where Owner == user` filter when it is not.

### `user` in a package's rule, in an app with no `[Principal]` {#package-user}
An app with no `[Principal]` can still use a package whose own `security { }` names `user` — `Osysharp.Blocks` asks
who started a preview with `CreatedBy == user`. There, **`user` is nobody**: the same null principal an anonymous
caller is in an app that has one. So the package's rule answers exactly as it does for a signed-out visitor:

- `X == user` and `X != user` hold for **no row** — not even a row whose `X` is empty. An `allow` built on one grants
  nothing; a `deny` built on one **applies**.
- A package rule that reads a property **of** the user (`user.Email`) is refused, naming the fix: nobody has an
  email, so the package needs you to declare a `[Principal]`.

Your OWN rule naming `user` is still a compile error in such an app — it has no one to mean, and the message says so
and offers the two ways out:

```osy syntax
entity Note {
  string Words;
  [CreatedBy] Guid? CreatedBy;
  security { allow read when IsAnonymous where CreatedBy == user; }
  //  ✗ 'user' is the signed-in caller, and this app declares no `[Principal]` entity — so nobody can ever sign in …
}
```

## `IsTheApp` — the app itself is acting   {#is-the-app}
Some code runs with nobody behind it: the platform starts it for the app. An `[On]` handler hears an event after the
request that raised it has ended; a workflow's body runs on a transition, a timer or a reminder; an `[OnStart]` hook
runs when the app starts. That code has the app's own authority and **no user** — so `IsAnonymous` is true there,
exactly as it is for a signed-out visitor. **`IsTheApp`** is what tells the two apart:

| who is asking | `IsAuthenticated` | `IsAnonymous` | `IsTheApp` |
|---|---|---|---|
| a signed-in person — a page, an API key, an integration's account, an MCP caller | true | false | **false** |
| a signed-out visitor | false | true | **false** |
| your sign-in and sign-up functions (`[AuthMethod]`), while they run | false | true | **false** |
| an `[On]` handler · a workflow's body (a transition, a timer, a clock, a reminder, a resume after a wait) · an `[OnStart]` hook | false | true | **true** |
| a `runas (Actor) { … }` block inside any of those | true | false | **false** — the block is the actor's ([[Actor] — a principal that code may run as](https://osysharp.com/reference/security/declared-actor/)) |
| a test's body, and a `[TestFixture]` | as the test's `[runas]` says | | **false** |

It is true for exactly the code that is already let past an `[Authorize]` — one fact, set by the platform when it
starts such a body, and never worked out from "nobody is signed in". Everything that arrives from outside is false.

**Where it is written.**

```osy title="in a function's body, on a function, in a policy" syntax
// In a function's body — the same check, wherever the function is called from.
int PlacesLeft(Session s) {
  if (!IsTheApp && !IsAuthenticated && !VisitorsSeePlaces) { throw new NotAuthorized("Sign in to see how full it is."); }
  return s.Capacity - Booking.Count(b => b.Session == s);
}

// On a function: only the app's own code reaches it. No request does, signed in or not.
[Authorize(IsTheApp)]
void CloseTheRegister(Session s) { … }

// In a policy, with whatever else admits a caller.
policy MaySeeCounts => IsTheApp || IsAuthenticated || VisitorsSeePlaces;
```

`IsAuthenticated` and `IsAnonymous` may be named in a function's body the same way.

**In an entity's `security { }` rule it grants nothing — and says so.** A rule is asked of whoever is calling (or of
an actor, inside its block), and a caller is never the app; the app's own code is not judged by an `allow` at all — it
reads and writes on its own authority. So `allow read when IsTheApp;` is a readable way to write "no caller reads
this; the app's own code does", and `allow read when IsTheApp || IsStaff;` is "staff, and the app's own code".

| written | what happens |
|---|---|
| `allow <verb> when IsTheApp …` | allowed; grants no caller anything |
| `deny <verb> when !IsTheApp` — directly or through a policy | **refused when compiled.** It would hold for everybody, the app's own code under a package's deny included. Write `deny <verb> except via <Function>;` ([deny … except via](https://osysharp.com/reference/security/except-via/)) and keep that function to the app with `[Authorize(IsTheApp)]` |
| `… where IsTheApp` | refused, like the other two: it is not a property of a row |
| `Security.Authorize(person, IsTheApp)` | refused: a question about a person, and a person is never the app. A policy that names `IsTheApp` answers no for any person asked about, whoever asks |
| on a page — `canSee:`, `if (Policy)` in a component | always no: a page is opened by a person |
| `policy IsTheApp => …` | refused — the name is reserved, like the other two |

```osy title="one function, asked by a caller and by the app's own handler" test app=security-is-the-app
[Principal] entity Member {
  [Required, MaxLength(120), Unique] string Email;
  security { allow read, create when IsAuthenticated || IsAnonymous; }
}

entity Reading {
  [Required, MaxLength(40)] string Station;
  int Level;
  security { allow read, create when IsAuthenticated || IsAnonymous; }
}

/// What the gauge said, and who asked for it to be written down.
entity LogLine {
  [Required, MaxLength(40)] string Station;
  [Required, MaxLength(20)] string WrittenBy;
  security { allow read, create when IsAuthenticated || IsAnonymous; }
}

[Event("Level read")]
public sealed class LevelRead { public Reading Reading; }

// Only the app's own code reaches this one.
[Authorize(IsTheApp)]
void WriteDown(string station, string by) { new LogLine { Station = station, WrittenBy = by }; }

string WhoAsks() { return IsTheApp ? "the app" : IsAnonymous ? "a visitor" : "a member"; }

[AllowAnonymous]
void Read(string station, int level) {
  var r = new Reading { Station = station, Level = level };
  Raise(new LevelRead { Reading = r });
}

[AllowAnonymous]
string Ask() { return WhoAsks(); }

[AllowAnonymous]
void WriteItMyself(string station) { WriteDown(station, WhoAsks()); }

// Heard after the commit that raised it, with the app's authority and nobody signed in.
[On]
void Log(LevelRead e) { WriteDown(e.Reading.Station, WhoAsks()); }
```

```osy title="a visitor is not the app; the handler is" run app=security-is-the-app
[Test]
void a_visitor_asks_and_the_apps_handler_asks() {
  Assert.Equal("a visitor", Ask());
  Assert.Throws<NotAuthorized>(() => WriteItMyself("North pier"));

  Read("North pier", 212);
  UnitOfWork.Commit();
  Background.Settle();

  Assert.Equal("the app", LogLine.Single(l => l.Station == "North pier").WrittenBy);
}
```

## Examples       {#examples}
A `[Principal]` with a credential column, a members-only directory read, and a genuinely public catalog:

```osy title="a members-only directory and a genuinely public notice" test app=security-principal-predicates
[Role] enum AppRole { Authenticator }
[RoleGrant] entity RoleGrant { User User; [Required] AppRole Role; }

// The leash the auth flow is checked against — the credential is masked from everyone EXCEPT this.
policy IsAuthenticator => RoleGrant.Any(g => g.User == user && g.Role == AppRole.Authenticator);

[Principal] entity User {
  string Email;
  security {
    allow read when IsAuthenticated;                 // signed-in users see the directory; the world does not
  }
}

entity Announcement {
  string Body;
  security {
    allow read when IsAuthenticated || IsAnonymous;  // truly public — everyone, said out loud
  }
}
```

An app with **no `[Principal]`** says "anyone" the same way — inline or through a `policy` — because the two
predicates are constants there: every caller is anonymous. (`when true` is refused in every app: a `when` asks who
the caller is, and a constant asks nothing. Its refusal names this spelling.)

```osy title="an app nobody signs in to: anyone may post, nobody is signed in" test app=security-principal-predicates-no-principal
policy Anyone => IsAnonymous || IsAuthenticated;   // true here: every caller is anonymous
policy SignedIn => IsAuthenticated;                // false here: nobody ever signs in

entity Notice {
  [MaxLength(200)] string Body;
  security { allow read, create when Anyone; }
}

entity Draft {
  [MaxLength(200)] string Body;
  security { allow read, create when SignedIn; }
}

[AllowAnonymous]
void Post(string body) { new Notice { Body = body }; }

[AllowAnonymous]
void Sketch(string body) { new Draft { Body = body }; }
```

```osy title="a visitor may post a notice, and may not keep a draft" run app=security-principal-predicates-no-principal
[Test]
void anyone_may_post_and_nobody_is_signed_in() {
  Post("Closed on Monday");
  UnitOfWork.Commit();
  Assert.Equal(1, Notice.Count());

  Assert.Denied(() => { Sketch("not yet"); UnitOfWork.Commit(); });
}
```

## See also       {#see-also}
- [security { }](https://osysharp.com/reference/security/entity-security/) — the `security { }` block these predicates live in
- [secure by default (deny-all)](https://osysharp.com/reference/security/secure-by-default/) — the deny-all posture that makes an unmentioned grant a denial
- [auth bootstrap (login, before anyone is signed in)](https://osysharp.com/reference/security/auth-bootstrap/) — login/signup before a principal exists, run as a declared role
- [[Actor] — a principal that code may run as](https://osysharp.com/reference/security/declared-actor/) — a block run as an actor is the actor's, not the app's
- [[On] — hear an event, after the commit that raised it](https://osysharp.com/reference/function/on/) — an event handler, which runs as the app
- [public pages (what a signed-out visitor can see and do)](https://osysharp.com/reference/security/public-reads/) — `IsAnonymous` in practice: what a signed-out visitor actually sees, and the half that is easy to forget
