Changelog
Guides

Sign in with an emailed code

Send a one-time code, verify it, and optionally create an account or accept a magic-link token.

Email-code sign-in stays off until you map it. AddPasswordAuth does not map these routes. AllowSignUp and the magic link default to off.

Request bodies, status codes, and option defaults are in the Email-code module.

Turn it on

  1. Map the entity and add a migration. MapEmailCodeEndpoints throws NotSupportedException if UseEmailCodes() was not called.
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.UseEmailCodes();
    }
    

    4.0 adds a Purpose column. Generate that migration before you deploy. See Upgrade to 4.0.
  2. Register IEmailCodeSender<TUser>. In Development, if you register none, AuthEndpoints logs the raw code with DevelopmentEmailCodeSender<TUser>. Outside Development, register your own sender. Startup fails when the sender is DevelopmentEmailCodeSender<TUser>, or when no sender is registered, unless RequireSenderOutsideDevelopment is false.
  3. Register email-code services and map send and verify on one group. Pass the session that verify should finish in. A missing AddEmailCodeEndpoints throws InvalidOperationException with the message Call AddEmailCodeEndpoints before MapEmailCodeEndpoints.
    builder.Services.AddAuthEndpointsIdentity<AppUser, AppDbContext>();
    builder.Services.AddCookieSessionEndpoints();
    builder.Services.AddAccountManagementEndpoints();
    builder.Services.AddEmailCodeEndpoints<AppUser, AppDbContext>();
    
    var app = builder.Build();
    app.UseAuthEndpoints();
    
    var identity = app.MapGroup("/identity");
    identity.MapEmailCodeEndpoints<AppUser>(AuthEndpointsSession.Cookie);
    identity.MapCookieSessionEndpoints<AppUser>();
    identity.MapAccountManagementEndpoints<AppUser>();
    

    A host that skips password register and login is Email-code only. That sample also turns the magic link on.
  4. On the 3.x facade, set EmailCode.Enabled to true instead of mapping the group yourself. That map keeps Identity bearer verify. It ignores SignInCompletionOptions.Default.
    builder.Services.AddAuthEndpoints<AppUser, AppDbContext>(o =>
    {
        o.EmailCode.Enabled = true;
    });
    

To issue a JWT, call AddJwtEndpoints, then pass AuthEndpointsSession.Jwt to MapEmailCodeEndpoints. Jwt.Enabled on the 3.x facade does not change this session. The obsolete completer types are in Upgrade to 4.0. Session results are in Sign-in completer.

Send a code

Send POST /identity/emailCode/send with { "email" }. A valid address returns 200 with { "expiresInSeconds", "resendAfterSeconds" }. The defaults are 10 minutes and 60 seconds.

The same 200 is returned for an unknown address, a locked account, and several other cases. The list is in the Email-code module.

Code is not HTML-encoded. Do not log Code or LinkUrl. POST /emailCode/send awaits SendSignInCodeAsync. Queue the message and return. A sender that waits on SMTP makes a known address slower than an unknown one.

Verify a code

Send POST /identity/emailCode/verify with { "email", "code" }. code is exactly CodeLength digits. The default length is 6.

For the cookie session in the sample, add ?useSessionCookies=true for a session cookie, or ?useSessionCookies=false for a persistent cookie. useCookies is ignored. The 200 body is empty. Identity bearer and JWT flags are in the Email-code module.

A correct code for an eligible confirmed user returns 200. Then call GET /identity/manage/info.

Wrong codes, a required second factor, and 405 on GET /emailCode/verify are in the Email-code module and Responses and errors.

Create an account from the code

Set AllowSignUp to true on AddEmailCodeEndpoints. On the 3.x facade, set it inside EmailCode.Configure. An unknown address is then mailed with purpose SignUp. The 200 send body does not change.

A correct code creates one user and signs that user in. The user fields, and what happens to an unconfirmed account, are in the Email-code module.

With AllowSignUp left at false, a correct code for an unknown address returns 401 and creates no user.

The magic link is off until you set MagicLink.Enabled to true. Set LinkUri, and in Production set AllowedOrigins. The URI rules are in the Email-code module.

The default link puts the token in the fragment. The fragment is not sent to your server. Read token in the client and post only that field:

{ "token": "…" }

A body that includes token together with email or code returns 400. A token-only body is 400 while the magic link is off, and the row is not consumed.

One successful verify consumes the row. A second verify of the same code or token returns 401.