OTP sign-in (email code)
Passwordless login: user enters email, receives a numeric code, submits it. Uses providers/otp (requires Adapter).
:::info Not MFA
This is sign-in OTP (POST /auth/callback/otp), not post-password MFA (POST /auth/mfa/verify). See OTP guide.
:::
Config + Fiber mount
import (
"github.com/izetmolla/goauth/adapters/postgres"
"github.com/izetmolla/goauth/providers/otp"
)
adapter := postgres.New(db)
auth, _ := goauth.New(goauth.Config{
Secret: []string{secret},
URL: "https://api.example.com",
Adapter: adapter,
Tokens: goauth.TokensConfig{Enabled: true, AlwaysReturn: true},
Providers: []goauth.Provider{
otp.New(otp.Options{
CodeLength: 6,
MaxAge: 600,
SendCode: func(ctx context.Context, p goauth.SendVerificationRequestParams) error {
// p.Identifier = email, p.Token = "482913"
return emailService.Send(p.Identifier, "Your code: "+p.Token)
},
}),
},
Pages: goauth.Pages{
VerifyRequest: "/check-email", // optional SPA route
},
})
app.All("/auth/*", fiberauth.Handler(auth))
Flow
sequenceDiagram
participant SPA
participant Fiber
participant goauth
SPA->>Fiber: POST /auth/signin/otp (email)
Fiber->>goauth: forward
goauth->>SPA: redirect /check-email or JSON
Note over goauth: SendCode(email, code)
SPA->>Fiber: POST /auth/callback/otp (email, code)
goauth->>SPA: tokens or cookie
SPA->>Fiber: GET /api/me + Bearer
Step 1 — request code (Fiber client)
async function requestLoginCode(email: string) {
const csrf = await fetch("/auth/csrf", { credentials: "include" })
.then((r) => r.json()).then((d) => d.csrfToken);
await fetch("/auth/signin/otp", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
credentials: "include",
body: new URLSearchParams({ email, csrfToken: csrf }),
});
}
With AlwaysReturn: true and token flow, CSRF may be skipped — check your config.
Step 2 — verify code
async function verifyLoginCode(email: string, code: string) {
const res = await fetch("/auth/callback/otp", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-Auth-Flow": "token",
Accept: "application/json",
},
body: new URLSearchParams({ email, code }),
});
return res.json(); // TokenResponse
}
Accepted code field names: code, otp, or token.
Protected route after OTP login
app.Get("/api/dashboard", fiberauth.Protect(auth), func(c fiber.Ctx) error {
s := fiberauth.SessionFrom(c)
return c.JSON(fiber.Map{"welcome": s.User.Name})
})
Demo: fixed code 123456
The fiberauth example pins OTP to 123456 for local testing:
GenerateVerificationToken: func() string { return "123456" },
SendVerificationRequest: func(_ context.Context, p goauth.SendVerificationRequestParams) error {
log.Printf("code for %s: %s", p.Identifier, p.Token)
return nil
},