sahat/hackathon-starter · error · Error

EMAIL_REQUIRED

EMAIL_REQUIRED

Error message

EMAIL_REQUIRED

What it means

Thrown when a new-user OAuth signup provides no usable email. The strategy normalizes providerProfile.email with validator.normalizeEmail; if the provider returned no email (or it normalizes to false/undefined), the app cannot create a local account because email is required.

Source

Thrown at config/passport.js:123

      if (user.profile.pictureSource === 'gravatar') {
        user.profile.picture = providerProfile.picture;
        user.profile.pictureSource = providerName;
      }
    }

    user.profile.location = user.profile.location || providerProfile.location;
    user.profile.website = user.profile.website || providerProfile.website;
    await user.save();
    return user;
  }
  // User is not logged in:
  const existingUser = await User.findOne({ [providerName]: { $eq: providerProfile.id } });
  if (existingUser) {
    return existingUser;
  }
  const normalizedEmail = providerProfile.email ? validator.normalizeEmail(providerProfile.email, { gmail_remove_dots: false }) : undefined;
  if (!normalizedEmail) {
    throw new Error('EMAIL_REQUIRED');
  }
  const existingEmailUser = await User.findOne({
    email: { $eq: normalizedEmail },
  });
  if (existingEmailUser) {
    throw new Error('EMAIL_COLLISION');
  }
  const user = new User();
  user.email = normalizedEmail;
  user[providerName] = providerProfile.id;
  req.user = user;
  if (oauth2provider) {
    await saveOAuth2UserTokens(req, accessToken, refreshToken, params.expires_in, refreshTokenExpiration, providerName, tokenConfig);
  } else {
    user.tokens.push({ kind: providerName, accessToken, ...(tokenSecret && { tokenSecret }) });
  }
  user.profile.name = providerProfile.name;
  user.profile.gender = providerProfile.gender;

View on GitHub (pinned to c12e339564)

Solutions

  1. Add the email scope to the passport strategy options (e.g. scope: ['user:email'] for GitHub, include_email:true for X)
  2. Prevent sign-in-with-email-less providers; restrict them to account linking only (sessionAlreadyLoggedIn path)
  3. Fork a custom strategy callback that prompts the user for an email before creating the account

Example fix

// before
passport.use(new GitHubStrategy({ clientID, clientSecret, scope: ['profile'] }, ...));
// after
passport.use(new GitHubStrategy({ clientID, clientSecret, scope: ['user:email'] }, ...));
Defensive patterns

Strategy: try-catch

Type guard

const hasEmail = (p) => typeof p?.email === 'string' && p.email.includes('@');

Try / catch

catch (e) { if (e.message === 'EMAIL_REQUIRED') { req.flash('errors', {msg:'Provider did not share an email. Sign up locally, then link this provider.'}); return res.redirect('/signup'); } throw e; }

Prevention

When it happens

Trigger: First-time sign-in with a provider that does not return an email (some X/Twitter apps, Steam, private GitHub accounts), so providerProfile.email is undefined and normalizedEmail is undefined.

Common situations: X (Twitter) app created with 'request email' scope missing, GitHub user with no public email and no user:email scope, or a provider API change that stopped returning the email field.

Related errors


AI-assisted analysis of sahat/hackathon-starter@c12e339564 (2026-08-27). Data as JSON: /api/errors/54a3562fdcee7279. Report an issue: GitHub.