# [AuthMethod] — a function an unauthenticated visitor may call

> `[AuthMethod]` marks a sign-in function — login, signup, password-reset — as reachable by a visitor who is not signed in. Everything else in your app refuses an unauthenticated caller before it runs, which is what you want; sign-in is the one flow that cannot require the thing it produces. The marker is checked against `app.AuthBootstrap` **both ways**: a marked function must be wired, and a wired function must be marked. So an anonymous entry point cannot exist by accident.

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

## Summary        {#summary}
A function marked **`[AuthMethod]`** may be called by a visitor who is **not signed in**. Every other function in your
app refuses an unauthenticated caller before its first statement runs — which is exactly what you want, and is why
sign-in needs a marker of its own: `Login` cannot require a signed-in user, because producing one is its job.

```osy title="the login function a signed-out visitor may call" test app=security-auth-method
[AuthMethod]
string Login(string email, string password) {
  var u = User.Where(x => x.Email == email).FirstOrDefault();
  // Spend the same time either way — an early return makes "no such account" measurably faster
  // than "wrong password", and that difference is an enumeration oracle anyone can time. The
  // one-argument form 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 "";           // no match → no ticket → the caller stays anonymous
}
```

The marker alone is not enough, and that is deliberate: the function must **also** be wired into
[`app.AuthBootstrap`](https://osysharp.com/reference/security/auth-bootstrap/) (as `Login`, `Signup` or `PasswordReset`). The two halves are checked
against each other, so you cannot get one without the other.

## Signature      {#signature}
```osy syntax
[AuthMethod] <ReturnType> <Name>(<params>) { … }   // callable while signed out; must be wired in app.AuthBootstrap
```

A `Login` or `Signup` returns the **session ticket** — a `string` from [`Security.IssueJwt`](https://osysharp.com/reference/stdlib/security/) — or
`""` for "no". A `PasswordReset` returns nothing of value **on purpose**: it mints a token, records it, and mails it —
and handing that token back to the caller instead would let anyone who knows an address take the account. Returning
nothing is the security property, not a shrug.

⚠ **All three verbs, or the flow cannot complete.** Mint and record and *send*. A reset that mints a token and stops
leaves the confirm step asking for a value nobody can obtain — and it will not look broken: it compiles, it
validates, and its tests can pass, because a test may read the token out of the row as the auth principal and the
locked-out person cannot. `osy lint` reports it as `security-reset-token-never-delivered` (MUST). The platform has no
mail verb; delivery is a [`client { }`](https://osysharp.com/reference/http/client/) you declare. See [[security-auth-bootstrap#examples]].

## Description    {#description}

### Do I need both `[AuthMethod]` and `app.AuthBootstrap`?   {#both-ways}
`[AuthMethod]` and `app.AuthBootstrap` must agree, and the compiler enforces it in **both directions**:

| You wrote | What happens | Why |
|---|---|---|
| `[AuthMethod]` on a function **not** wired in `app.AuthBootstrap` | **compile error** | It is an anonymously-reachable entry point that no sign-in flow uses. That is a hole, and a hole is never intentional. |
| A function wired in `app.AuthBootstrap` **without** `[AuthMethod]` | **compile error** | The signed-out visitor would be refused before your login could run — a login page that can never log anyone in. |
| Both | it works | The only way to get an anonymous entry point is to say so twice. |

There is no `[AllowAnonymous]` on a function. That marker belongs to pages ([page authorization (policies)](https://osysharp.com/reference/ui/authorize/)); a function opens to the
world only through this one purpose-built door, so `grep AuthMethod` is a complete list of your app's anonymous entry
points. It should be a short list.

### What an auth method may touch   {#the-leash}
An `[AuthMethod]` does **not** run with special powers. It runs as the **ephemeral principal** described in
[auth bootstrap (login, before anyone is signed in)](https://osysharp.com/reference/security/auth-bootstrap/) — a caller bearing the one `[Role]` you named, and **no user**. What it may read and write
is decided by your ordinary [`security { }`](https://osysharp.com/reference/security/entity-security/) grants for that role, and nothing else:

```osy title="the leash — the auth role is granted exactly what login needs, and no more" test app=security-auth-method
[Role] enum AppRole { Authenticator, Member, Admin }

[Principal]
entity User {
  [Required, Unique, MaxLength(255)] string Email;
  [MaxLength(200)] string PasswordHash;
  security {
    allow read   when IsAuthenticator;             // login must find the user and check the hash
    allow create when IsAuthenticator;             // signup must create one
    allow read where Id == user.Id;                // and a signed-in user reads their own row
    deny read PasswordHash when !IsAuthenticator;  // nobody else EVER reads the hash — not even an admin
  }
}

entity RoleGrant {
  [Required] User User;
  [Required] AppRole Role = AppRole.Member;
  security { allow create when IsAuthenticator; }  // the auth flow may mint a grant…
}

entity Invoice {                                    // …and it may not touch anything else.
  [Required] decimal Total;
  security { allow read where User.Any(u => u.Id == user.Id); }
}

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

`Login` above can read `User` — including `PasswordHash`, which the field mask hands to no one else. It cannot read
an `Invoice`, because nothing granted `Authenticator` a read on one. If you never grant the auth role a write, no
routed method can be turned into a writer, however the function is written. **The leash is your own rules**, which is
the whole point: the bootstrap path is not a hole you have to remember — it is subject to the same grants you can read
on the entity.

### It cannot elevate a caller who is already signed in   {#no-elevation}
The ephemeral principal is minted **only** when the caller has no authenticated ticket. An already-signed-in user who
calls `Login` is *not* elevated — they run as themselves, with their own grants. So the bootstrap path can never be
used by an ordinary user to borrow the auth role's access.

### What should a login RETURN, and what does a failure return?   {#ticket}
A `Login`/`Signup` ends by returning what [`Security.IssueJwt(userId, email)`](https://osysharp.com/reference/stdlib/security/) produced. That string
is the session ticket, scoped to your app and that user. The login page hands it to `Session.SignIn(ticket)`, which
stores it as the session bearer, so every later request is authenticated as that user.

**A failed sign-in returns `""`** — not an exception, not a partial ticket. `Session.SignIn("")` stores nothing and the
visitor stays anonymous. Returning the same empty answer for "no such user" and "wrong password" is also what keeps
`Login` from telling a stranger which email addresses have accounts.

## Examples       {#examples}
Signup, wired as the second auth method — it creates the credential and signs the new user straight in:

```osy title="signup: create the credential, issue the ticket" test app=security-auth-method
[AuthMethod]
string Signup(string email, string password) {
  var u = new User { Email = email, PasswordHash = Security.HashPassword(password) };
  var grant = new RoleGrant { User = u, Role = AppRole.Member };
  return Security.IssueJwt(u.Id, u.Email);
}

[Page("/login")]
[AllowAnonymous]
[Render(CSR)]
component LoginPage() {
  string email = "";
  string password = "";
  action SignIn() { Session.SignIn(Login(email, password)); }
  render {
    Input(value: email, placeholder: "Email");
    Input(value: password, placeholder: "Password", type: "password");
    Button("Sign in", onPress: SignIn);
  }
}

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

Both `Login` and `Signup` are marked **and** wired — the pairing the compiler insists on. `Signup` hashes the password
(never storing the plaintext), grants the ordinary `Member` role, and returns a ticket, so signing up *is* signing in.
For the variant where the **first** account bootstraps an admin, see [role grants (and the first admin)](https://osysharp.com/reference/security/role-grants/).

## See also       {#see-also}
- [auth bootstrap (login, before anyone is signed in)](https://osysharp.com/reference/security/auth-bootstrap/) — the wiring an `[AuthMethod]` must appear in, and the ephemeral principal it runs as
- [Security.* — hashing, verifying, tickets, random ids](https://osysharp.com/reference/stdlib/security/) — `HashPassword` · `VerifyPassword` · `IssueJwt`
- [role grants (and the first admin)](https://osysharp.com/reference/security/role-grants/) — granting a role during signup without opening a path to self-elevation
- [security { }](https://osysharp.com/reference/security/entity-security/) — the `security { }` grants that leash the auth role
- [app.Auth — how the platform authenticates a user of your app](https://osysharp.com/reference/security/password-auth/) — `app.Auth`, the *other* way a user of your app can be authenticated
