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 hasDescription#
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:
| Slot | The step |
|---|---|
Login · Signup | sign in · make an account |
PasswordResetRequest · PasswordReset · PasswordChange | ask for a reset link · use it · change a known password |
EmailChange · EmailVerifyRequest · EmailVerify · EmailVerifyCode · EmailChangeCancel | change the address · ask for a proof · prove it by link · by code · call a move off from the old address |
SecondFactorSignIn · SecondFactorEnable · SecondFactorDisable · SecondFactorRecoveryCodes | two-step sign-in |
OAuthSignIn · OAuthSignup · OAuthLink · OAuthUnlink | a provider (Google, GitHub) |
AcceptInvite | join by invitation |
PhoneVerify · PhoneSignIn · SmsSecondStep | a phone proven by a texted code |
EmailDeliveryProblem | whether mail to an address bounced, before anybody signs in |
MachineSignIn | a program trades a refresh token for a ticket (POST /api/app-auth/console/machine-token) |
CiSignIn | a CI job signs in by the OpenID Connect token its CI signed, verified by the platform on an OidcIssuer endpoint |
PasskeyChallenge · PasskeySignIn · PasskeyAdd · PasskeyRemove · PasswordRemove | passkeys, 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#
- [AuthMethod] — a function an unauthenticated visitor may call — what
[AuthMethod]itself means, and the both-ways wiring check - auth bootstrap (login, before anyone is signed in) — the slots, and the principal a wired method runs as
- The UserAccounts kit — sign-in done properly, once — a package that states its own wiring