SignIn callback
Callbacks.SignIn runs after the provider authenticated the user but before a session is created. Return false to reject (Auth.js signIn callback).
Signature
SignIn: func(ctx context.Context, p goauth.SignInCallbackParams) (bool, error)
SignInCallbackParams
type SignInCallbackParams struct {
User *User
Account *Account
Profile Profile // raw OAuth profile map
Credentials map[string]string // credentials provider only
}
| Field | OAuth | Credentials | Email/OTP | Passkey |
|---|---|---|---|---|
User | From Profile | From Authorize | From adapter | From adapter |
Account | Provider tokens | Synthetic | Email provider | Passkey |
Profile | Userinfo JSON | Empty | Empty | Empty |
Credentials | Empty | Form fields | Empty | Empty |
Example: allow only verified company email
SignIn: func(ctx context.Context, p goauth.SignInCallbackParams) (bool, error) {
if !strings.HasSuffix(p.User.Email, "@acme.com") {
return false, nil // 403 AccessDenied
}
return true, nil
},
Example: block banned users (your database)
SignIn: func(ctx context.Context, p goauth.SignInCallbackParams) (bool, error) {
banned, err := db.IsBanned(ctx, p.User.Email)
if err != nil {
return false, err // 500 with CallbackRouteError wrapper
}
return !banned, nil
},
Example: require email on GitHub
SignIn: func(ctx context.Context, p goauth.SignInCallbackParams) (bool, error) {
if p.Account != nil && p.Account.Provider == "github" {
if p.User.Email == "" {
return false, errors.New("GitHub account must have a public email")
}
}
return true, nil
},
Example: credentials — log attempt (do not validate password here)
Password validation belongs in Authorize, not SignIn:
// credentials provider
Authorize: func(ctx context.Context, creds map[string]string, r *http.Request) (*goauth.User, error) {
return validate(creds["email"], creds["password"])
},
// SignIn — policy only
SignIn: func(ctx context.Context, p goauth.SignInCallbackParams) (bool, error) {
if p.Credentials["email"] == "" {
return false, nil
}
return true, nil
},
Error vs false
| Return | HTTP result |
|---|---|
(false, nil) | 403 AccessDenied |
(false, err) | Error response with message |
(true, nil) | Continue sign-in |
MFA interaction
SignIn runs before MFA. If it returns true and MFA is enabled, the client receives a challenge instead of a session — MFA is not a second SignIn check.
sequenceDiagram
participant C as Client
participant G as goauth
C->>G: POST /callback/credentials
G->>G: Authorize OK
G->>G: SignIn callback true
G->>C: MFA challenge (not session yet)
C->>G: POST /mfa/verify
G->>C: Session issued
SignIn is not called again at MFA verify — the challenge JWE already embeds the user.