Summary
When an external OAuth provider returns no email and the provider is not configured as email-optional, the OAuth callback rejects the sign-in with a generic internal server error:
https://github.com/supabase/auth/blob/master/internal/api/external.go#L175-L177
if len(userData.Emails) == 0 && !emailOptional {
return apierrors.NewInternalServerError("Error getting user email from external provider")
}
NewInternalServerError hardcodes HTTP 500 with error_code = unexpected_failure. This has two downsides:
- It mis-classifies a non-server fault as a 500. The provider simply didn't return an email — the server is working fine. The redirect already carries
error=server_error, which reads like an outage.
- Clients have no stable way to detect this specific case. Since the only distinguishing signal is the human-readable
error_description string, clients (e.g. mobile SDKs) are forced to substring-match "Error getting user email from external provider" to tell "provider returned no email" apart from any other internal failure.
There is already a dedicated, unused error code
errorcode.go defines email_address_not_provided, but it is not referenced anywhere in the codebase:
https://github.com/supabase/auth/blob/master/internal/api/apierrors/errorcode.go#L105
ErrorCodeEmailAddressNotProvided ErrorCode = "email_address_not_provided"
Proposal
Use the existing code via NewUnprocessableEntityError (422), consistent with how nearby external-identity conditions are reported (e.g. ErrorCodeSignupDisabled uses 422):
return apierrors.NewUnprocessableEntityError(apierrors.ErrorCodeEmailAddressNotProvided, "Error getting user email from external provider")
For the OAuth redirect flow this keeps error=server_error and error_description unchanged (422 is not in oauthErrorMap, so it falls back to server_error), and only upgrades error_code from unexpected_failure to email_address_not_provided — so existing clients are unaffected while new clients gain a stable code to branch on.
Motivation
This surfaces with Sign in with Apple, where a user can withhold the email scope. With "Allow users without an email" disabled, the provider returns no email and Auth correctly rejects the sign-in — but downstream SDKs can't cleanly tell that apart from a real server error, so they fall back to a generic "something went wrong" message.
Side note / question (not part of the proposed fix)
The native grant_type=id_token path (token_oidc.go) passes emailOptional into createAccountFromExternalIdentity but does not seem to have the equivalent empty-email rejection that the web /callback path has. Is the email requirement intended to be enforced for the id_token grant too, or is that deliberately left to account-linking? Happy to open a separate issue if it's worth looking into.
I have a PR ready for the proposed change.
Summary
When an external OAuth provider returns no email and the provider is not configured as email-optional, the OAuth callback rejects the sign-in with a generic internal server error:
https://github.com/supabase/auth/blob/master/internal/api/external.go#L175-L177
NewInternalServerErrorhardcodes HTTP 500 witherror_code = unexpected_failure. This has two downsides:error=server_error, which reads like an outage.error_descriptionstring, clients (e.g. mobile SDKs) are forced to substring-match"Error getting user email from external provider"to tell "provider returned no email" apart from any other internal failure.There is already a dedicated, unused error code
errorcode.godefinesemail_address_not_provided, but it is not referenced anywhere in the codebase:https://github.com/supabase/auth/blob/master/internal/api/apierrors/errorcode.go#L105
Proposal
Use the existing code via
NewUnprocessableEntityError(422), consistent with how nearby external-identity conditions are reported (e.g.ErrorCodeSignupDisableduses 422):For the OAuth redirect flow this keeps
error=server_erroranderror_descriptionunchanged (422 is not inoauthErrorMap, so it falls back toserver_error), and only upgradeserror_codefromunexpected_failuretoemail_address_not_provided— so existing clients are unaffected while new clients gain a stable code to branch on.Motivation
This surfaces with Sign in with Apple, where a user can withhold the email scope. With "Allow users without an email" disabled, the provider returns no email and Auth correctly rejects the sign-in — but downstream SDKs can't cleanly tell that apart from a real server error, so they fall back to a generic "something went wrong" message.
Side note / question (not part of the proposed fix)
The native
grant_type=id_tokenpath (token_oidc.go) passesemailOptionalintocreateAccountFromExternalIdentitybut does not seem to have the equivalent empty-email rejection that the web/callbackpath has. Is the email requirement intended to be enforced for the id_token grant too, or is that deliberately left to account-linking? Happy to open a separate issue if it's worth looking into.I have a PR ready for the proposed change.