Credentials
Password or arbitrary field-based sign-in. Mirrors Auth.js Credentials provider.
Important rules
- Sessions are always JWT (or bearer tokens) — never database strategy.
- Sign-in endpoint:
POST /auth/callback/credentials(not GET). - Works with MFA globally or selectively from Authorize.
Simple example
import "github.com/izetmolla/goauth/providers/credentials"
credentials.New(credentials.Options{
Fields: []goauth.CredentialField{
{Name: "email", Label: "Email", Type: "email"},
{Name: "password", Label: "Password", Type: "password"},
},
Authorize: func(ctx context.Context, creds map[string]string, r *http.Request) (*goauth.User, error) {
u, ok := validatePassword(creds["email"], creds["password"])
if !ok {
return nil, nil // rejected sign-in
}
return u, nil
},
})
curl -X POST http://localhost:3000/auth/callback/credentials \
-d "email=demo@example.com&password=secret"
Selective MFA from Authorize
After a successful password check, opt in to MFA for specific users. Configure MFA.SendCode (adapter recommended). MFA.Enabled can stay false.
credentials.New(credentials.Options{
Authorize: func(ctx context.Context, creds map[string]string, r *http.Request) (*goauth.User, error) {
dbUser, err := api.Validate(creds["email"], creds["password"])
if dbUser == nil {
return nil, err
}
user := &goauth.User{ID: dbUser.ID, Email: dbUser.Email, Phone: dbUser.Phone}
if api.UserRequiresMFA(dbUser, r) {
return credentials.RequireMFA(
user,
goauth.VerificationEmail,
goauth.MaskEmail(dbUser.Email),
), nil
}
return user, nil
},
})
The client receives JSON with mfaRequired, channel, and destination — see Selective MFA guide.
Phone MFA
return credentials.RequireMFA(
user,
goauth.VerificationPhone,
goauth.MaskPhone(user.Phone),
user.Phone,
), nil
Manual flags
user.RequireMFA = true
user.MFADelivery = &goauth.MFADeliveryHint{
Channel: goauth.VerificationEmail,
Destination: goauth.MaskEmail(user.Email),
}
return user, nil
Global MFA + token flow
When MFA.Enabled: true, every login is challenged (unless device trusted):
# Step 1 — password OK, MFA required
curl -X POST http://localhost:3000/auth/callback/credentials \
-H "X-Auth-Flow: token" \
-H "Accept: application/json" \
-d "email=demo@example.com&password=secret"
# → { "challenge": "eyJ...", "expiresIn": 600, "mfaRequired": true, "channel": "email", "destination": "d***@example.com" }
# Step 2 — verify OTP
curl -X POST http://localhost:3000/auth/mfa/verify \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "challenge=eyJ...&code=123456&trustDevice=true"
# → { accessToken, refreshToken, sessionId, user }
Authorize contract
| Return | Meaning |
|---|---|
(*User, nil) | Success — session issued (or MFA challenge if RequireMFA) |
(nil, nil) | Invalid credentials (generic failure) |
(nil, err) | CredentialsSignin error |
| User field | Set in Authorize | Effect |
|---|---|---|
RequireMFA | true | Triggers MFA after password OK |
MFADelivery | Channel + masked destination | Returned in challenge JSON |
Never put passwords in JWT claims — only run Authorize server-side.