Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Security

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

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

`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.

stable2 examples compiled by CIsecurityauthenticationconfig

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.

[Principal]
entity User {
  [Required, 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#

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#

app.Auth and [AuthMethod] are two different 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
Who runs the sign-inthe platform, genericallyyour function, written in Osy#
You writetwo field bindingsa Login function (and usually a Signup)
Who calls itthe tooling — provisioning a user, the console/API loginyour app's own login page
Hashing / verifying / the ticketthe platform does ityou do it, with Security.*
Custom rules (invite-gated signup, a first-admin grant, a lockout)not possible — it is generic by designanything 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#

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#

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:

app.Auth = [
  new PasswordAuth { LoginField = Email, PasswordField = PasswordHash },
  new OAuthAuth { Client = Google },
];

Examples#

The full shape, as a real app declares it — the generic binding and the app's own login, side by side:

[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);

// 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();
  if (u == null) { return ""; }
  if (Security.VerifyPassword(password, u.PasswordHash)) { return Security.IssueJwt(u.Id, u.Email); }
  return "";
}

app.AuthBootstrap = new AuthBootstrap { Role = AppRole.Authenticator, Login = Login };

(The User entity and the app.Auth line are the ones at the top of this page — the two declarations coexist in the same app, and neither knows about the other.)

See also#

Related

auth bootstrap (login, before anyone is signed in)

Under deny-all, login faces a paradox: it must read a user row *before* anyone is authenticated. `app.AuthBootstrap`…

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

Security.* — hashing, verifying, tickets, random ids

The calls an authentication flow needs: `HashPassword` (salted, one-way), `VerifyPassword` (constant-work comparison…

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…