# principal predicates (IsAuthenticated / IsAnonymous) 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.

<!-- 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` and `IsAnonymous` are **reserved** — a
`policy` may not take those names.

## 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, or silently cancels the grant above it. A FIELD-scoped one (`deny read Secret;`) stays legal: property rules replace the entity's rather than layering on them, so it is the only way to say "nobody, ever". ⚠ 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.

## 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 }
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
  }
}
```

## 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
- [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
