MFA & trusted devices (Fiber)
When MFA.Enabled is true, credentials sign-in returns a challenge instead of tokens until the user submits an OTP.
Config
MFA: goauth.MFAConfig{
Enabled: true,
SendCode: func(ctx context.Context, p goauth.MFASendCodeParams) error {
return sms.Send(p.Email, "Code: "+p.Code)
},
IsDeviceTrusted: func(ctx context.Context, p goauth.MFADeviceTrustParams) (bool, error) {
return trustDB.IsTrusted(p.UserID, p.DeviceID)
},
TrustDevice: func(ctx context.Context, p goauth.MFADeviceTrustParams) error {
return trustDB.Trust(p.UserID, p.DeviceID, time.Now().Add(90*24*time.Hour))
},
},
app.All("/auth/*", fiberauth.Handler(auth))
SPA flow with deviceId
const DEVICE_ID = localStorage.getItem("deviceId") ?? crypto.randomUUID();
localStorage.setItem("deviceId", DEVICE_ID);
// Before login UI — skip MFA screen if trusted
const trust = await fetch(
`/auth/mfa/device?userId=${userId}&deviceId=${DEVICE_ID}`
).then((r) => r.json());
if (trust.skipMfa) {
// show password only; expect direct session after POST credentials
}
// Step 1 — password
const r1 = await fetch("/auth/callback/credentials", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded", "X-Auth-Flow": "token" },
body: new URLSearchParams({ email, password, deviceId: DEVICE_ID }),
});
const step1 = await r1.json();
if (step1.challenge) {
// Step 2 — MFA
await fetch("/auth/mfa/verify", {
method: "POST",
body: new URLSearchParams({
challenge: step1.challenge,
code: userEnteredCode,
trustDevice: "true",
deviceId: DEVICE_ID,
}),
});
}
Fiber: optional wrapper route
Expose a thin API that proxies to goauth (same origin):
app.Get("/api/mfa/device-trusted", func(c fiber.Ctx) error {
userID := c.Query("userId")
deviceID := c.Query("deviceId")
trusted, err := auth.IsMFADeviceTrusted(c.Context(), userID, deviceID, nil)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"trusted": trusted, "skipMfa": trusted})
})
Or let the SPA call GET /auth/mfa/device directly (already mounted on fiberauth.Handler).
Programmatic check in Fiber middleware
app.Post("/api/login/precheck", func(c fiber.Ctx) error {
email := c.FormValue("email")
deviceID := c.FormValue("deviceId")
u, _ := userDB.ByEmail(email)
if u == nil {
return c.JSON(fiber.Map{"skipMfa": false})
}
trusted, _ := auth.IsMFADeviceTrusted(c.Context(), u.ID, deviceID, nil)
return c.JSON(fiber.Map{"skipMfa": trusted})
})
See MFA security doc.