amruthpillai/reactive-resume · error · BetterAuthError

${providerName} provider did not return an email address. Th

Error message

${providerName} provider did not return an email address. This is required for user creation.

What it means

createProfileMapper throws BetterAuthError with cause 'EMAIL_REQUIRED' when the OAuth provider's profile object has no email. Reactive Resume keys accounts on email, so a missing email blocks user creation. The error is thrown inside the provider profile-mapping callback during sign-in / sign-up.

Source

Thrown at packages/auth/src/oauth-profile.ts:172

interface OAuthMapperOptions<TProfile extends OAuthProfile> {
	providerName: string;
	findExistingUser?: (profile: TProfile, context: OAuthMapperContext) => Promise<ExistingOAuthUser | undefined>;
	getPreferredUsername?: (profile: TProfile, context: OAuthMapperContext) => string | undefined | null;
	getName?: (profile: TProfile, context: OAuthMapperContext) => string | undefined | null;
	getImage?: (profile: TProfile) => string | undefined | null;
}

export function createProfileMapper<TProfile extends OAuthProfile>({
	providerName,
	findExistingUser,
	getPreferredUsername,
	getName,
	getImage,
}: OAuthMapperOptions<TProfile>) {
	return async (profile: TProfile) => {
		if (!profile.email) {
			throw new BetterAuthError(
				`${providerName} provider did not return an email address. This is required for user creation.`,
				{ cause: "EMAIL_REQUIRED" },
			);
		}

		const email = profile.email.trim().toLowerCase();
		const emailLocalPart = getEmailLocalPart(email);
		const context = { email, emailLocalPart };
		const existingUser = (await findExistingUser?.(profile, context)) ?? (await findExistingUserByEmail(email));
		const image = getImage?.(profile) ?? undefined;

		if (existingUser) {
			const existingEmail = existingUser.email.trim().toLowerCase();
			await normalizeExistingUserEmail(existingUser.id, existingUser.email, existingEmail);

			return {
				id: existingUser.id,
				name: existingUser.name,

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Ensure the email scope is requested in the provider config (e.g. GitHub needs 'user:email').
  2. Ask the user to make a primary email visible on the provider, or sign in with a different provider/email-password.
  3. If extending a custom provider, implement getEmail to read email from the correct nested profile field.
  4. Surface a friendly 'We need an email address to create your account' prompt instead of leaking the raw BetterAuthError.

Example fix

// before: missing email scope
genericOAuth({ scopes: ['openid','profile'] })
// after: request email
genericOAuth({ scopes: ['openid','profile','email'] })
// and/or a custom mapper
getImage: (p) => p.avatar_url,
// ensure findExistingUserByEmail handles the chosen email
Defensive patterns

Strategy: try-catch

Validate before calling

// before sign-in, prompt user: 'Ensure your provider account exposes an email address.'
// configure provider scopes to include email (e.g. GitHub 'user:email').

Type guard

function hasEmail(profile: unknown): profile is { email: string } {
  return !!profile && typeof (profile as any).email === 'string' && (profile as any).email.length > 0;
}

Try / catch

try { await auth.api.signInSocial({ provider, callbackURL }); }
catch (e) {
  if (/did not return an email/i.test(String((e as Error).message))) {
    ui.error('We need an email from this provider. Make one visible or use another sign-in method.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: GitHub sign-in where the user hid their email and no private email is exposed; a provider configured without the email scope; a custom provider whose profile payload omits email; the provider returns email:null for an unverified address.

Common situations: GitHub users with private emails; mis-scoped Microsoft/Apple/LinkedIn providers; a new OAuth integration whose mapper doesn't extract email from a nested field; legacy accounts without a verified email.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/0b44227d86a992e4. Report an issue: GitHub.