Auth Error Codes
Complete reference for all authentication error codes returned by EdgeBase.
Error Response Format
All authentication errors return a consistent JSON structure:
{
"code": 400,
"message": "Human-readable error message.",
"data": {}
}
| Field | Type | Description |
|---|---|---|
code | number | HTTP status code (400, 401, 403, 404, 409, 429, 500, 503) |
message | string | Human-readable description of the error |
data | object | Optional additional context (e.g., { "captcha_required": true }) |
Sign Up Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | Email and password are required. | Missing email or password field in request body | Show form validation — highlight empty fields |
| 400 | Invalid email format... | Email fails format validation | Show email-specific validation message |
| 400 | Password must be at least 8 characters. | Password shorter than 8 characters | Show password requirements hint |
| 409 | Email already registered. | An account with this email already exists | Suggest signing in or resetting password |
| 429 | Too many signup attempts... | Signup rate limit exceeded | Show retry timer with countdown |
| 403 | Auth hook 'beforeSignUp' rejected the operation. | beforeSignUp hook returned a rejection | Display hook's custom message if provided |
Sign In Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | Email and password are required. | Missing email or password field | Show form validation |
| 401 | Invalid credentials. | Wrong email or wrong password | Show generic error — do not reveal which field is incorrect |
| 403 | This account uses OAuth sign-in. Password login is not available. | User registered via OAuth, not email/password | Redirect user to the appropriate OAuth provider |
| 403 | Auth hook 'beforeSignIn' rejected the operation. | beforeSignIn hook returned a rejection | Display hook's custom message if provided |
| 429 | Too many login attempts... | Sign-in rate limit exceeded (per email) | Show retry timer with countdown |
OAuth Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | Unsupported OAuth provider: {provider} | Provider name not recognized | Check provider name spelling |
| 400 | OAuth provider {provider} is not enabled. | Provider not listed in allowedOAuthProviders config | Enable provider in server config |
| 500 | OAuth provider {provider} is not configured. | Missing clientId or clientSecret env vars | Set required environment variables |
| 400 | OAuth error: {description} | OAuth provider returned an error in callback | Display the provider's error description |
| 400 | Missing code or state parameter. | Callback URL missing required query params | Restart the OAuth flow |
| 400 | Invalid or expired OAuth state. | State token expired (5 min TTL) or CSRF mismatch | Restart the OAuth flow |
| 400 | OAuth state provider mismatch. | State token was issued for a different provider | Restart the OAuth flow with the correct provider |
| 409 | This OAuth account is already linked to another user. | OAuth identity already associated with a different account | Sign in with the linked account instead |
| 409 | Email is already registered to another account. | Email from OAuth provider conflicts with existing account | Link accounts or sign in with existing account |
Anonymous Auth Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 404 | Anonymous authentication is not enabled. | anonymousAuth is disabled in server config | Enable anonymous auth in config or use a different auth method |
Session & Token Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | Refresh token is required. | Missing refresh token in request body | Re-authenticate the user |
| 401 | Invalid refresh token. | JWT decoding failed — malformed or tampered token | Clear stored tokens and re-authenticate |
| 401 | Refresh token expired. | Token TTL exceeded | Clear stored tokens and re-authenticate |
| 401 | Refresh token reuse detected. Session revoked. | A previously used refresh token was replayed — possible token theft. All sessions for the user are revoked | Clear stored tokens and force full re-authentication |
| 401 | Authentication required. | Missing or invalid Authorization header | Redirect to sign-in page |
When EdgeBase detects a refresh token being used more than once, it assumes the token was stolen. All sessions for that user are immediately revoked as a security measure. The legitimate user will need to sign in again.
Email Verification Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | Verification token is required. | Missing token in request | Check the verification URL format |
| 400 | Invalid or expired verification token. | Token not found in the database | Request a new verification email |
| 400 | Verification token has expired. Please request a new one. | Token TTL exceeded (24 hours) | Prompt user to request a new verification email |
Password Reset Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | Email is required. | Missing email in reset request | Show form validation |
| 400 | Password reset token is required. | Missing token in reset confirmation | Check the reset URL format |
| 400 | New password is required. | Missing new password in reset confirmation | Show form validation |
| 400 | Password must be at least 8 characters. | New password too short | Show password requirements |
| 400 | Invalid or expired password reset token. | Token not found in the database | Request a new password reset email |
| 400 | Password reset token has expired. Please request a new one. | Token TTL exceeded (1 hour) | Prompt user to request a new reset email |
| 403 | Password reset was blocked by the beforePasswordReset hook. | beforePasswordReset hook returned a rejection | Display hook's custom message if provided |
Change Password Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | currentPassword and newPassword are required. | Missing current or new password | Show form validation |
| 400 | Password must be at least 8 characters. | New password too short | Show password requirements |
| 401 | Current password is incorrect. | Wrong current password entered | Prompt user to re-enter current password |
| 403 | This account uses OAuth sign-in. Password login is not available. | Account was created via OAuth — no password set | Password change is not applicable for OAuth accounts |
| 403 | Anonymous accounts cannot change password. | Anonymous user attempted password change | Link account with email first via account linking |
Account Linking Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | Email and password are required. | Missing fields when linking with email/password | Show form validation |
| 400 | Password must be at least 8 characters. | Weak password during email link | Show password requirements |
| 400 | Only anonymous users can link with OAuth. | Non-anonymous user attempted OAuth linking | OAuth linking is only available for anonymous accounts |
| 409 | Email is already registered. | Email already in use by another account | Suggest signing in with that email instead |
| 409 | This OAuth account is already linked. | OAuth identity already associated with another account | Sign in with the linked account instead |
Profile Update Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 400 | emailVisibility must be 'public' or 'private'. | Invalid value for emailVisibility field | Use only 'public' or 'private' |
| 400 | No valid fields to update... | Request body contains no recognized fields | Check available profile fields in the docs |
Admin API Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 403 | Service Key required for admin auth operations. | Admin endpoint called without a Service Key | Include the Service Key in the request header |
| 401 | Unauthorized. Service Key required. | Invalid or expired Service Key provided | Verify your Service Key in server config |
| 404 | User not found. | User ID does not exist in the database | Verify the user ID before retrying |
Captcha Errors
| Status | Message | When | Handling |
|---|---|---|---|
| 403 | Captcha verification required. | Captcha is enabled but no token was provided. Response includes data.captcha_required: true | Render captcha widget and retry with the token |
| 403 | Captcha verification failed. | Captcha token is invalid or expired | Reset captcha widget and prompt user to retry |
| 503 | Captcha service unavailable. | Cloudflare Turnstile API is unreachable and failMode is closed | Show a temporary error and retry after a delay |
data fieldWhen captcha verification is required, the error response includes extra context:
{
"code": 403,
"message": "Captcha verification required.",
"data": { "captcha_required": true }
}
Use the data.captcha_required flag to programmatically detect when to render the captcha widget.
Rate Limiting
| Status | Message | When | Handling |
|---|---|---|---|
| 429 | Too many requests. Try again later. | Global rate limit exceeded | Back off and retry after a delay |
| 429 | Too many signup attempts... | Signup-specific rate limit exceeded | Show retry timer |
| 429 | Too many login attempts... | Sign-in-specific rate limit exceeded (per email) | Show retry timer |
Sign-in rate limits are tracked per email address to prevent brute-force attacks against specific accounts while not affecting other users.
SDK Error Handling
All EdgeBase client SDKs throw structured errors that include the code and message fields from the server response.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#
- C++
try {
const { user } = await client.auth.signIn({
email: 'user@example.com',
password: 'wrongPassword',
});
} catch (error) {
switch (error.code) {
case 401:
// Invalid credentials — show generic error
showError('Email or password is incorrect.');
break;
case 429:
// Rate limited — show retry message
showError('Too many attempts. Please try again later.');
break;
case 403:
if (error.message.includes('OAuth')) {
// OAuth-only account — redirect to OAuth provider
redirectToOAuth();
} else if (error.data?.captcha_required) {
// Captcha required — render widget
showCaptcha();
}
break;
default:
showError(error.message);
}
}
try {
final result = await client.auth.signIn(
email: 'user@example.com',
password: 'wrongPassword',
);
} on EdgeBaseError catch (error) {
switch (error.code) {
case 401:
// Invalid credentials — show generic error
showError('Email or password is incorrect.');
break;
case 429:
// Rate limited — show retry message
showError('Too many attempts. Please try again later.');
break;
case 403:
if (error.message.contains('OAuth')) {
// OAuth-only account — redirect to OAuth provider
redirectToOAuth();
} else if (error.data?['captcha_required'] == true) {
// Captcha required — render widget
showCaptcha();
}
break;
default:
showError(error.message);
}
}
do {
let result = try await client.auth.signIn(
email: "user@example.com",
password: "wrongPassword"
)
} catch let error as EdgeBaseError {
switch error.code {
case 401:
// Invalid credentials — show generic error
showError("Email or password is incorrect.")
case 429:
// Rate limited — show retry message
showError("Too many attempts. Please try again later.")
case 403:
if error.message.contains("OAuth") {
// OAuth-only account — redirect to OAuth provider
redirectToOAuth()
} else if error.data?["captcha_required"] as? Bool == true {
// Captcha required — render widget
showCaptcha()
}
default:
showError(error.message)
}
}
try {
val result = client.auth.signIn(
email = "user@example.com",
password = "wrongPassword"
)
} catch (error: EdgeBaseError) {
when (error.code) {
401 -> {
// Invalid credentials — show generic error
showError("Email or password is incorrect.")
}
429 -> {
// Rate limited — show retry message
showError("Too many attempts. Please try again later.")
}
403 -> {
if (error.message.contains("OAuth")) {
// OAuth-only account — redirect to OAuth provider
redirectToOAuth()
} else if (error.data?.get("captcha_required") == true) {
// Captcha required — render widget
showCaptcha()
}
}
else -> showError(error.message)
}
}
try {
Map<String, Object> result = client.auth().signIn("user@example.com", "wrongPassword");
} catch (EdgeBaseException error) {
switch (error.getCode()) {
case 401:
// Invalid credentials — show generic error
showError("Email or password is incorrect.");
break;
case 429:
// Rate limited — show retry message
showError("Too many attempts. Please try again later.");
break;
case 403:
if (error.getMessage().contains("OAuth")) {
// OAuth-only account — redirect to OAuth provider
redirectToOAuth();
} else if (Boolean.TRUE.equals(error.getData().get("captcha_required"))) {
// Captcha required — render widget
showCaptcha();
}
break;
default:
showError(error.getMessage());
}
}
try
{
var result = await client.Auth.SignInAsync("user@example.com", "wrongPassword");
}
catch (EdgeBaseException error)
{
switch (error.Code)
{
case 401:
// Invalid credentials — show generic error
ShowError("Email or password is incorrect.");
break;
case 429:
// Rate limited — show retry message
ShowError("Too many attempts. Please try again later.");
break;
case 403:
if (error.Message.Contains("OAuth"))
{
// OAuth-only account — redirect to OAuth provider
RedirectToOAuth();
}
else if (error.Data?.ContainsKey("captcha_required") == true)
{
// Captcha required — render widget
ShowCaptcha();
}
break;
default:
ShowError(error.Message);
break;
}
}
try {
auto result = client.auth().signIn("user@example.com", "wrongPassword");
} catch (const edgebase::Error& error) {
switch (error.code()) {
case 401:
// Invalid credentials — show generic error
showError("Email or password is incorrect.");
break;
case 429:
// Rate limited — show retry message
showError("Too many attempts. Please try again later.");
break;
case 403:
if (error.message().find("OAuth") != std::string::npos) {
// OAuth-only account — redirect to OAuth provider
redirectToOAuth();
}
break;
default:
showError(error.message());
}
}
Quick Reference by Status Code
| Status | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Missing required fields, invalid format, expired tokens |
| 401 | Unauthorized | Invalid credentials, expired/invalid tokens, missing auth header |
| 403 | Forbidden | Hook rejections, OAuth-only accounts, captcha required, anonymous restrictions |
| 404 | Not Found | User not found, feature not enabled |
| 409 | Conflict | Duplicate email, OAuth account already linked |
| 429 | Too Many Requests | Rate limits (global, signup, or signin) |
| 500 | Internal Server Error | Missing OAuth provider configuration |
| 503 | Service Unavailable | External service unreachable (e.g., Turnstile API) |