# app.Auth — how the platform authenticates a user of your app

> `app.Auth` binds two properties of your `[Principal]` — which one is the login, which one holds the password hash — and with that the platform can create and authenticate a user of your app **without you writing any code**: it hashes, verifies and issues the ticket itself. It is what the tooling (provisioning a first user, the console login) runs on. It is NOT what your own `[AuthMethod] Login` uses — that function does its own verifying — and knowing which is which is the difference between two auth surfaces and one confusing one.

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

## Summary        {#summary}
**`app.Auth`** declares how a user of your app is authenticated **generically** — by the platform, with no code from
you. For a password, you bind two properties of your [[security-entity-security|`[Principal]`]] entity: the one that
carries the login, and the one that stores the hash.

```osy title="bind the login and the hash — that is the whole declaration" test app=security-password-auth
[Principal]
entity User {
  // [Unique] or two rows share a login and WHICH account you sign into is undefined — and no check-then-insert in
  // application code can close that race, because the race is in the database.
  [Required, Unique, MaxLength(255)] string Email;
  [MaxLength(200)] string PasswordHash;
  security {
    allow read when IsAuthenticated;
    allow read where Id == user.Id;
    // A read grant is a ROW grant, so without this the hash rides out with the row — to an admin listing users,
    // to an export, to the user's own page.
    //
    // CONDITIONED, and the condition is the sign-in itself. A masked column is DROPPED from the read, so an
    // unconditional deny would hide the hash from the verification too and refuse a correct password exactly as it
    // refuses a wrong one. Sign-in happens while you are still ANONYMOUS — and an anonymous caller already gets no
    // rows here, so this one line says "verify me, then never show me" with nothing else to declare.
    deny read PasswordHash when IsAuthenticated;
  }
}

app.Auth = new PasswordAuth { LoginField = Email, PasswordField = PasswordHash };
```

That is enough for the platform to **create** a user of your app and **authenticate** one: it hashes the password,
verifies it, and issues the ticket, reading and writing the fields you named. The field names come from this
declaration — nothing about `Email` or `PasswordHash` is hardcoded, and your properties may be called anything.

## Signature      {#signature}
```osy syntax
app.Auth = new PasswordAuth { LoginField = <PrincipalProp>, PasswordField = <PrincipalProp> };
app.Auth = new OAuthAuth { Client = <OAuthClient> };
app.Auth = [ new PasswordAuth { … }, new OAuthAuth { … } ];   // several methods, offered together
```

`LoginField` and `PasswordField` are both **required**, and each must **name a property of the `[Principal]` entity**
(a bare name, not a string) — an unknown name is a compile error, so the binding cannot rot when you rename a
property. `app.Auth` is a **singleton**: declare it once. Its value may be a single method or a **list**, when an app
offers more than one way to sign in.

## Description    {#description}

### `app.Auth` and `[AuthMethod]` are two different doors   {#two-doors}
This is the distinction to get right, and the one that reads as confusing until you see it:

| | **`app.Auth`** (`PasswordAuth`) | **[[security-auth-method|`[AuthMethod]`]]** + [`app.AuthBootstrap`](https://osysharp.com/reference/security/auth-bootstrap/) |
|---|---|---|
| **Who runs the sign-in** | the **platform**, generically | **your function**, written in Osy# |
| **You write** | two field bindings | a `Login` function (and usually a `Signup`) |
| **Who calls it** | the tooling — provisioning a user, the console/API login | your app's own login page |
| **Hashing / verifying / the ticket** | the platform does it | you do it, with [`Security.*`](https://osysharp.com/reference/stdlib/security/) |
| **Custom rules** (invite-gated signup, a first-admin grant, a lockout) | not possible — it is generic by design | anything you can write |

They are **not alternatives to choose between**. A real app usually declares **both**, and for good reason: `app.Auth`
gives the platform a way to create and authenticate a user of your app before you have built any of it (and keeps
`osy`-side tooling working forever after), while your `[AuthMethod] Login` is the sign-in your users actually see,
where you control the flow. The platform's own Admin app — the most complete app we have — declares both.

Concretely: your `[AuthMethod] Login` does *not* consult `app.Auth`. It reads the user, calls
`Security.VerifyPassword`, and returns `Security.IssueJwt(...)` itself. Nothing is shared between the two paths except
the rows in your `[Principal]` table — which is exactly why they interoperate: a user the platform created with
`app.Auth` can sign in through your login page, and a user your `Signup` created can be authenticated by the tooling.
**Both hash passwords the same way**, so the hash column is meaningful to both.

### Why the platform needs the binding at all   {#why-binding}
The platform does not know what your user entity looks like. It cannot assume a property called `Email`, or that the
hash lives in `PasswordHash` — your app might have `Username` and `Secret`, or a Norwegian app might call it
`Epost`. `app.Auth` is the two-line answer to "which column do I compare, and which one do I hash into", and it is
compile-checked against the `[Principal]`, so it cannot drift out of step with the entity it names.

Declare no `app.Auth`, and the generic path simply reports that the app declares no password method — the tooling
cannot provision or authenticate a user. Your own `[AuthMethod] Login` still works, because it never needed it.

### Offering OAuth instead of, or beside, a password   {#oauth}
`new OAuthAuth { Client = <name> }` authenticates against an OAuth client you declared in `app.OAuthClients`, instead
of (or alongside) a password. Give a list to `app.Auth` when an app offers both, and the caller picks:

```osy title="offer a password AND an OAuth provider" syntax
app.Auth = [
  new PasswordAuth { LoginField = Email, PasswordField = PasswordHash },
  new OAuthAuth { Client = Google },
];
```

## Examples       {#examples}
The full shape, as a real app declares it — the generic binding *and* the app's own login, side by side:

```osy title="both doors, in one app" test app=security-password-auth-both-doors
[Role] enum AppRole { Authenticator, Member }

entity RoleGrant {
  [Required] User User;
  [Required] AppRole Role = AppRole.Member;
  security { allow create when IsAuthenticator; }
}

policy IsAuthenticator => RoleGrant.Any(g => g.User == user && g.Role == AppRole.Authenticator);

[Principal]
entity User {
  [Required, Unique, MaxLength(255)] string Email;
  [MaxLength(200)] string PasswordHash;
  security {
    allow read when IsAuthenticated;
    // ⚑ BY ROLE, not by row. `Login` runs as the armed AuthBootstrap principal, which holds no row of its own —
    //   so a `where` filter answers it with nothing and the login can never find the account it was called to
    //   check. A role predicate is answerable with no rows at all, which is why it works where a filter cannot.
    allow read when IsAuthenticator;
    // ⚑ AND THE CONDITION NAMES THAT SAME ROLE. With a `[AuthMethod]` login the sign-in is NOT anonymous (it is
    //   that armed principal), so the `when IsAuthenticated` spelling at the top of this page — right for a
    //   PasswordAuth-only app — would fire during login here and refuse every correct password, silently.
    deny read PasswordHash when !IsAuthenticator;
  }
}

// The app's OWN login — it verifies and issues the ticket itself; app.Auth is not involved.
[AuthMethod]
string Login(string email, string password) {
  var u = User.Where(x => x.Email == email).FirstOrDefault();
  // ⚑ SPEND THE SAME TIME EITHER WAY. Returning early on "no such account" makes the refusal FASTER than a wrong
  //   password, and that difference is an account-enumeration oracle anyone can time. The one-argument
  //   `VerifyPassword` verifies against nothing, costs a full KDF, and answers false.
  if (u == null) { Security.VerifyPassword(password); return ""; }
  if (Security.VerifyPassword(password, u.PasswordHash)) { return Security.IssueJwt(u.Id, u.Email); }
  return "";
}

app.Auth = new PasswordAuth { LoginField = Email, PasswordField = PasswordHash };
app.AuthBootstrap = new AuthBootstrap { Role = AppRole.Authenticator, Login = Login };
```

The two declarations coexist and neither knows about the other. What DOES differ from the summary fence at the top of
this page is the `security {}` block, and the difference is not cosmetic: an app whose only door is `app.Auth` signs
you in while you are still **anonymous**, so `deny read PasswordHash when IsAuthenticated;` never fires during
verification. An `[AuthMethod]` login runs as the principal `app.AuthBootstrap` arms — which IS authenticated — so
here the same line would refuse every correct password. Condition on the auth ROLE, as above, and both doors work.

## See also       {#see-also}
- [[AuthMethod] — a function an unauthenticated visitor may call](https://osysharp.com/reference/security/auth-method/) — your own login function, and why it needs no `app.Auth`
- [auth bootstrap (login, before anyone is signed in)](https://osysharp.com/reference/security/auth-bootstrap/) — the wiring that lets a signed-out visitor reach that function
- [Security.* — hashing, verifying, tickets, random ids](https://osysharp.com/reference/stdlib/security/) — the hashing, verifying and ticket-issuing your own login does by hand
- [security { }](https://osysharp.com/reference/security/entity-security/) — the `[Principal]` entity whose fields this binds
