Osy#betaRun it on Osyrin: osyrin.com

Reference / Security

[AuthMethod(AuthSlot.X)] — the sign-in slot a method is written for

[AuthMethod(AuthSlot.EmailVerifyCode)] string ConfirmCode(string email, string code) { … }

`[AuthMethod(AuthSlot.X)]` says which `app.AuthBootstrap` slot a sign-in method is written for. A package that ships sign-in methods but states no bootstrap of its own declares them this way, so an app that forgets one is told the exact line to add — and a method wired into a slot it was not written for is warned. Bare `[AuthMethod]` stays legal.

stable2 examples compiled by CIsecurityauthenticationpackages

Summary#

An [AuthMethod] may name the slot of app.AuthBootstrap it is written for: [AuthMethod(AuthSlot.Login)], [AuthMethod(AuthSlot.EmailVerifyCode)]. Nothing about who may call it changes — the wiring still decides that ([[security-auth-method#both-ways]]). What changes is what the compiler can tell you: a used package's method your app forgot to wire is reported with the one line that wires it, and a method wired into a slot it was not written for is reported before anyone signs in through it.

[Role] enum AppRole { Authenticator, Member }

[Principal]
entity User {
  [Required, Unique, MaxLength(255)] string Email;
  [MaxLength(200)] string PasswordHash;
  security {
    allow read when IsAuthenticated;
    allow read, create when IsAuthenticator;
    deny read PasswordHash when !IsAuthenticator;
  }
}

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

[AuthMethod(AuthSlot.Login)]
string SignIn(string email, string password) {
  var u = User.Where(x => x.Email == email).FirstOrDefault();
  if (u == null) { Security.VerifyPassword(password); return ""; }
  if (Security.VerifyPassword(password, u.PasswordHash)) { return Security.IssueJwt(u.Id, u.Email); }
  return "";
}

Signature#

[AuthMethod]                     // callable signed out once a slot names it — the slot is not stated
[AuthMethod(AuthSlot.<Slot>)]    // …and written for <Slot>, one of the slots app.AuthBootstrap has

Description#

Which slots are there?#

Exactly the method slots of app.AuthBootstrap — the argument is a closed set, and a name outside it is refused with every one of them listed:

SlotThe step
Login · Signupsign in · make an account
PasswordResetRequest · PasswordReset · PasswordChangeask for a reset link · use it · change a known password
EmailChange · EmailVerifyRequest · EmailVerify · EmailVerifyCode · EmailChangeCancelchange the address · ask for a proof · prove it by link · by code · call a move off from the old address
SecondFactorSignIn · SecondFactorEnable · SecondFactorDisable · SecondFactorRecoveryCodestwo-step sign-in
OAuthSignIn · OAuthSignup · OAuthLink · OAuthUnlinka provider (Google, GitHub)
AcceptInvitejoin by invitation
PhoneVerify · PhoneSignIn · SmsSecondStepa phone proven by a texted code
EmailDeliveryProblemwhether mail to an address bounced, before anybody signs in
MachineSignIna program trades a refresh token for a ticket (POST /api/app-auth/console/machine-token)
CiSignIna CI job signs in by the OpenID Connect token its CI signed, verified by the platform on an OidcIssuer endpoint
PasskeyChallenge · PasskeySignIn · PasskeyAdd · PasskeyRemove · PasswordRemovepasskeys, and turning password sign-in off

Why would a package declare it?#

A package that ships a whole sign-in flow and states the wiring itself (as Osysharp.UserAccounts does) already tells the compiler where each method goes. A package that ships the methods and leaves the wiring to the app — a shop whose customers have accounts of the shop's own — could not: when a store left one step unwired, the most the warning could say was "wire it as one of these twenty-one". Declaring the slot on the method lets it say the line:

// in the package
[AuthMethod(AuthSlot.EmailVerifyCode)]
public string ConfirmEmailCode(string email, string code) { … }

// the app that wires its bootstrap and forgets that step is told, at its app.AuthBootstrap:
//   … it is written for the `EmailVerifyCode` slot (`[AuthMethod(AuthSlot.EmailVerifyCode)]`) — add
//   `EmailVerifyCode = ConfirmEmailCode` to `app.AuthBootstrap` if this app offers that step.

The warning is AUTHMETHOD_NOT_WIRED, and it fires only when the step can actually be reached — your code calls the method, or a package page or component your app shows does. A package page your app takes over without a [Route] is not served, so the steps only it shows say nothing.

What if it is wired into another slot?#

Each slot calls its method with that slot's own arguments, so a method written for EmailVerifyCode will not work as EmailVerify. Wiring it there is a warning, AUTHMETHOD_SLOT_MISMATCH, naming both slots and the line that puts it right (Wire it as EmailVerifyCode = ConfirmEmailCode.). It is a warning rather than an error because the program still compiles to exactly what you wrote.

Examples#

The app's own methods may declare their slots too — the compiler then holds the wiring to them:

[AuthMethod(AuthSlot.Signup)]
string MakeAccount(string email, string password) {
  var u = new User { Email = email, PasswordHash = Security.HashPassword(password) };
  new RoleGrant { User = u, Role = AppRole.Member };
  return Security.IssueJwt(u.Id, u.Email);
}

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

Both are wired where they say they belong, so nothing is said. Swap them — Login = MakeAccount — and the compiler warns that a method written for Signup is standing in for a sign-in.

See also#

Related

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

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

The UserAccounts kit — sign-in done properly, once

`Osysharp.UserAccounts` is the default way into an app: sign-in with a password or a provider (Google, GitHub, …)…