Forgot / reset password
goauth has no single ForgotPassword provider — you compose email magic link or OTP plus your own reset UI and database update.
Pattern A — magic link (email provider)
User requests reset → email with link → your SPA sets new password.
Config
import "github.com/izetmolla/goauth/providers/email"
email.New(email.Options{
SendVerificationRequest: func(ctx context.Context, p goauth.SendVerificationRequestParams) error {
// Point users to YOUR reset page with token in query
resetURL := "https://app.example.com/reset-password?token=" +
url.QueryEscape(p.Token) + "&email=" + url.QueryEscape(p.Identifier)
return mailer.Send(p.Identifier, "Reset password", resetURL)
},
}),
Fiber routes
app.All("/auth/*", fiberauth.Handler(auth))
// Your app (not goauth):
app.Post("/api/auth/forgot-password", forgotPasswordHandler) // triggers signin/email
app.Post("/api/auth/reset-password", resetPasswordHandler) // validates token + updates DB
Trigger magic link via goauth
// "Forgot password" form posts to goauth email sign-in
await fetch("/auth/signin/email", {
method: "POST",
body: new URLSearchParams({ email }),
credentials: "include",
});
User opens link → GET /auth/callback/email?email=...&token=... creates session. For reset-only, do not auto-login: handle token in your API instead of using the default email callback for session creation.
Advanced: store reset tokens in your DB; use email provider only to send the link; verify token in POST /api/auth/reset-password.
Pattern B — OTP then new password (recommended with Fiber API)
POST /auth/signin/otp— send code- User enters code + new password in SPA
- Your Fiber handler verifies code via adapter or completes OTP sign-in then
POST /api/auth/set-passwordwhile authenticated
Step 1 — OTP (goauth)
await fetch("/auth/signin/otp", {
method: "POST",
body: new URLSearchParams({ email }),
});
Step 2 — verify + set password (your Fiber route)
app.Post("/api/auth/reset-with-code", func(c fiber.Ctx) error {
email := c.FormValue("email")
code := c.FormValue("code")
newPass := c.FormValue("password")
// Option 1: consume goauth verification token via adapter (same as otp callback)
vt, err := adapter.UseVerificationToken(c.Context(), email, code)
if err != nil {
return c.Status(400).JSON(fiber.Map{"error": "invalid code"})
}
_ = vt
if err := userDB.SetPassword(c.Context(), email, newPass); err != nil {
return c.Status(500).JSON(fiber.Map{"error": "update failed"})
}
return c.JSON(fiber.Map{"ok": true})
})
Option 2 — OTP sign-in then change password (authenticated)
const tokens = await verifyLoginCode(email, code);
// user is signed in
await fetch("/api/auth/password", {
method: "POST",
headers: { Authorization: `Bearer ${tokens.accessToken}` },
body: JSON.stringify({ password: newPassword }),
});
app.Post("/api/auth/password",
fiberauth.Protect(auth),
func(c fiber.Ctx) error {
s := fiberauth.SessionFrom(c)
pass := c.FormValue("password")
return updatePassword(c.Context(), s.User.ID, pass)
},
)
Redirect after reset
Pages: goauth.Pages{
SignIn: "/login",
},
After successful reset, redirect client to /login?reset=ok.
// In reset handler:
return c.Redirect().To("/login?reset=success")
Security notes
| Practice | Why |
|---|---|
Rate-limit forgot endpoints | Prevent email flooding |
| Constant-time user lookup | Don't reveal if email exists |
Short OTP MaxAge | 10 minutes default |
| Invalidate sessions after password change | Optional DELETE /auth/sessions |
See Session management.