Skip to main content

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
}
FieldOAuthCredentialsEmail/OTPPasskey
UserFrom ProfileFrom AuthorizeFrom adapterFrom adapter
AccountProvider tokensSyntheticEmail providerPasskey
ProfileUserinfo JSONEmptyEmptyEmpty
CredentialsEmptyForm fieldsEmptyEmpty

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

ReturnHTTP 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.