Skip to main content

Error Reference

GoAuth uses structured error responses across all modules. Every error returned by the library is a *types.GoAuthError, providing a machine-readable code, a human-readable message, and an HTTP status code.

GoAuthError Struct​

type GoAuthError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
StatusCode int `json:"-"`
Details any `json:"details,omitempty"`
Err error `json:"-"`
}
FieldTypeJSONDescription
CodeErrorCode (string)"code"Machine-readable error code (e.g. "USER_NOT_FOUND")
Messagestring"message"Human-readable error description
StatusCodeinthidden (json:"-")HTTP status code used in the response
Detailsany"details" (omitted if nil)Additional context (e.g. 2FA challenge data)
Errerrorhidden (json:"-")Wrapped underlying error for debugging

Methods​

Error() string -- Implements the error interface. Returns Message if no wrapped error, or "Message: wrapped error" if one exists.

Wrap(err error) *GoAuthError -- Sets the underlying error and returns the same GoAuthError for chaining. Use this to attach a root cause while preserving the structured error:

return types.NewDatabaseError().Wrap(err)

Unwrap() error -- Returns the underlying error, enabling errors.Is() and errors.As() to traverse the error chain:

if errors.Is(authErr.Unwrap(), sql.ErrNoRows) {
// handle missing row
}

Constructor​

func NewGoAuthError(code ErrorCode, message string, statusCode int) *GoAuthError

Creates a custom GoAuthError with any code, message, and status code. Most common errors have dedicated factory functions (listed below).

Standard Error Response​

All error responses follow this JSON shape:

{
"code": "ERROR_CODE",
"message": "Human-readable description of the error",
"details": null
}

The details field is omitted from the response when nil. It is populated for specific errors like TWO_FACTOR_REQUIRED, where it carries challenge metadata.

The HTTP status code is set on the response header, not in the JSON body.


Error Codes by Category​

User Errors​

ConstantCodeHTTP StatusDefault Message
ErrUserNotFoundUSER_NOT_FOUND404 Not FoundUser not found
ErrUserAlreadyExistsUSER_ALREADY_EXISTS409 ConflictUser with this email already exists
ErrUserNotActiveUSER_NOT_ACTIVE403 ForbiddenUser account is not active
ErrUserBlockedUSER_BLOCKED403 ForbiddenUser account is blocked
ErrEmailNotVerifiedEMAIL_NOT_VERIFIED403 ForbiddenEmail address not verified
ErrPhoneNotVerifiedPHONE_NOT_VERIFIED403 ForbiddenPhone number not verified
ErrAccountDeactivatedACCOUNT_DEACTIVATED403 ForbiddenAccount has been deactivated
ErrEmailAlreadyVerifiedEMAIL_ALREADY_VERIFIED400 Bad RequestEmail address already verified
ErrPhoneAlreadyVerifiedPHONE_ALREADY_VERIFIED400 Bad RequestPhone number already verified
ErrPhoneAlreadyInUsePHONE_ALREADY_IN_USE400 Bad RequestPhone number already in use
ErrUsernameAlreadyExistsUSERNAME_ALREADY_EXISTS400 Bad RequestUsername already exists

Authentication Errors​

ConstantCodeHTTP StatusDefault Message
ErrInvalidCredentialsINVALID_CREDENTIALS401 UnauthorizedInvalid email or password
ErrInvalidTokenINVALID_TOKEN401 UnauthorizedInvalid or expired token
ErrTokenExpiredTOKEN_EXPIRED401 UnauthorizedToken has expired
ErrTokenNotFoundTOKEN_NOT_FOUND400 Bad RequestToken not found
ErrTokenAlreadyUsedTOKEN_ALREADY_USED400 Bad RequestToken already used
ErrInvalidRefreshTokenINVALID_REFRESH_TOKEN401 UnauthorizedInvalid refresh token
ErrAccountLockedACCOUNT_LOCKED423 LockedAccount is temporarily locked due to too many failed attempts
ErrPasswordExpiredPASSWORD_EXPIRED401 UnauthorizedPassword has expired

Session Errors​

ConstantCodeHTTP StatusDefault Message
ErrInvalidSessionINVALID_SESSION401 UnauthorizedInvalid session
ErrSessionNotFoundSESSION_NOT_FOUND404 Not FoundSession not found
ErrSessionExpiredSESSION_EXPIRED401 UnauthorizedSession has expired
ErrSessionInvalidSESSION_INVALID401 UnauthorizedSession is invalid

Validation Errors​

ConstantCodeHTTP StatusDefault Message
ErrInvalidEmailINVALID_EMAIL400 Bad RequestInvalid email format
ErrInvalidPasswordINVALID_PASSWORD400 Bad RequestInvalid password format
ErrInvalidPhoneINVALID_PHONE400 Bad RequestInvalid phone number format
ErrMissingFieldsMISSING_FIELDS400 Bad RequestMissing required fields
ErrInvalidRequestBodyINVALID_REQUEST_BODY400 Bad Request(custom message)
ErrInvalidJSONINVALID_JSON400 Bad RequestInvalid JSON format
ErrValidationVALIDATION_ERROR(not assigned by default)(not assigned by default)
ErrInvalidRequestIPINVALID_REQUEST_IP(not assigned by default)(not assigned by default)

ErrMissingFields accepts a fields parameter: when provided, the message becomes "Missing required fields: email, password".

ErrInvalidRequestBody is used by NewValidationError(message) which accepts a custom message.

Authorization Errors​

ConstantCodeHTTP StatusDefault Message
ErrUnauthorizedUNAUTHORIZED401 UnauthorizedUnauthorized access
ErrForbiddenFORBIDDEN403 ForbiddenAccess forbidden
ErrInvalidCSRFINVALID_CSRF403 ForbiddenInvalid CSRF token
ErrCaptchaRequiredCAPTCHA_REQUIRED403 ForbiddenCaptcha token required
ErrCaptchaFailedCAPTCHA_FAILED403 ForbiddenCaptcha verification failed

Two-Factor Authentication Errors​

ConstantCodeHTTP StatusDefault Message
ErrTwoFactorRequiredTWO_FACTOR_REQUIRED200 OKTwo-factor authentication required
ErrTwoFactorAlreadyEnabledTWO_FACTOR_ALREADY_ENABLED400 Bad RequestTwo-factor authentication is already enabled
ErrTwoFactorNotEnabledTWO_FACTOR_NOT_ENABLED400 Bad RequestTwo-factor authentication is not enabled
ErrTwoFactorNotFoundTWO_FACTOR_NOT_FOUND400 Bad RequestTwo-factor authentication not found
ErrTwoFactorInvalidTWO_FACTOR_INVALID400 Bad RequestTwo-factor authentication is invalid
ErrTwoFactorExpiredTWO_FACTOR_EXPIRED400 Bad RequestTwo-factor authentication has expired
ErrTwoFactorAlreadyUsedTWO_FACTOR_ALREADY_USED400 Bad RequestTwo-factor authentication has already been used

TWO_FACTOR_REQUIRED uses HTTP 200 intentionally -- it is not an error but a challenge response. The details field carries challenge metadata (e.g. available 2FA methods).

OAuth Errors​

ConstantCodeHTTP StatusDefault Message
ErrOAuthProviderNotFoundOAUTH_PROVIDER_NOT_FOUND400 Bad RequestOAuth provider '<provider>' not found or not configured
ErrOAuthProviderDisabledOAUTH_PROVIDER_DISABLED400 Bad RequestOAuth provider '<provider>' is disabled
ErrOAuthInvalidStateOAUTH_INVALID_STATE400 Bad RequestInvalid OAuth state parameter
ErrOAuthStateExpiredOAUTH_STATE_EXPIRED400 Bad RequestOAuth state has expired. Please try again.
ErrOAuthStateUsedOAUTH_STATE_USED400 Bad RequestOAuth state has already been used
ErrOAuthTokenExchangeOAUTH_TOKEN_EXCHANGE_FAILED400 Bad RequestFailed to exchange authorization code for tokens
ErrOAuthUserInfoOAUTH_USER_INFO_FAILED400 Bad RequestFailed to retrieve user information from OAuth provider
ErrOAuthEmailExistsOAUTH_EMAIL_EXISTS409 ConflictAn account with this email already exists
ErrOAuthSignupDisabledOAUTH_SIGNUP_DISABLED403 ForbiddenSignup via OAuth is disabled
ErrOAuthProviderErrorOAUTH_PROVIDER_ERROR400 Bad RequestOAuth provider returned an error
ErrOAuthNotLinkedOAUTH_NOT_LINKED400 Bad RequestOAuth provider '<provider>' is not linked to this account
ErrOAuthAlreadyLinkedOAUTH_ALREADY_LINKED409 ConflictOAuth provider '<provider>' is already linked to this account
ErrOAuthEmailRequiredOAUTH_EMAIL_REQUIRED400 Bad RequestEmail address is required from OAuth provider
ErrOAuthAccountLinkingDisabledOAUTH_ACCOUNT_LINKING_DISABLED403 ForbiddenAccount linking via OAuth is disabled

Some OAuth factory functions accept a provider or message parameter that is interpolated into the default message.

Organization Errors​

ConstantCodeHTTP StatusDefault Message
ErrOrgNotFoundORG_NOT_FOUND404 Not FoundOrganization not found
ErrOrgSlugTakenORG_SLUG_TAKEN409 ConflictOrganization slug is already taken
ErrOrgMemberExistsORG_MEMBER_EXISTS409 ConflictUser is already a member of this organization
ErrOrgMemberNotFoundORG_MEMBER_NOT_FOUND404 Not FoundOrganization member not found
ErrOrgNotMemberORG_NOT_MEMBER403 ForbiddenYou are not a member of this organization
ErrOrgInsufficientRoleORG_INSUFFICIENT_ROLE403 ForbiddenInsufficient role for this operation
ErrOrgCannotRemoveOwnerORG_CANNOT_REMOVE_OWNER403 ForbiddenCannot remove the organization owner
ErrOrgMaxMembersORG_MAX_MEMBERS403 ForbiddenOrganization has reached the maximum number of members
ErrInvitationNotFoundINVITATION_NOT_FOUND404 Not FoundInvitation not found
ErrInvitationExpiredINVITATION_EXPIRED400 Bad RequestInvitation has expired
ErrInvitationExistsINVITATION_ALREADY_EXISTS409 ConflictA pending invitation already exists for this email
ErrInvitationEmailMismatchINVITATION_EMAIL_MISMATCH403 ForbiddenYour email does not match the invitation

Verification Errors​

ConstantCodeHTTP StatusDefault Message
ErrInvalidVerificationTokenINVALID_VERIFICATION_TOKEN400 Bad RequestInvalid verification token
ErrVerificationTokenExpiredVERIFICATION_TOKEN_EXPIRED400 Bad RequestVerification token has expired
ErrInvalidVerificationCodeINVALID_VERIFICATION_CODE400 Bad RequestInvalid verification code
ErrVerificationCodeExpiredVERIFICATION_CODE_EXPIRED400 Bad RequestVerification code has expired

System Errors​

ConstantCodeHTTP StatusDefault Message
ErrInternalErrorINTERNAL_ERROR500 Internal Server ErrorAn unexpected error occurred
ErrDatabaseErrorDATABASE_ERROR500 Internal Server ErrorDatabase operation failed
ErrEmailSendFailedEMAIL_SEND_FAILED500 Internal Server ErrorFailed to send email
ErrSmsSendFailedSMS_SEND_FAILED500 Internal Server ErrorFailed to send SMS
ErrConfigurationErrorCONFIGURATION_ERROR500 Internal Server ErrorConfiguration error
ErrMethodNotAllowedMETHOD_NOT_ALLOWED405 Method Not AllowedMethod not allowed

Other Errors​

ConstantCodeHTTP StatusDefault Message
ErrUnknownUNKNOWN_ERROR500 Internal Server ErrorAn unexpected error occurred
ErrCustomCUSTOM_ERROR(caller-defined)(caller-defined)

NewCustomError(message) creates an error with no HTTP status code set -- the caller is expected to set StatusCode before returning it to an HTTP handler.


Handling Errors in Client Code​

Checking Error Codes​

if authErr != nil && authErr.Code == types.ErrUserNotFound {
// handle user not found
}

Wrapping Errors​

user, err := db.FindByEmail(ctx, email)
if err != nil {
return types.NewDatabaseError().Wrap(err)
}

Using errors.Is with Wrapped Errors​

authErr := someOperation()
if authErr != nil && errors.Is(authErr, sql.ErrNoRows) {
// the underlying cause was a missing DB row
}

Factory Functions with Parameters​

Several factory functions accept parameters for dynamic messages:

types.NewMissingFieldsError("email, password")
// Message: "Missing required fields: email, password"

types.NewOAuthProviderNotFoundError("github")
// Message: "OAuth provider 'github' not found or not configured"

types.NewInternalError("failed to hash password")
// Message: "failed to hash password"

types.NewValidationError("password must be at least 8 characters")
// Message: "password must be at least 8 characters"