Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub user.email can be null #374

Closed
arunoda opened this issue Jul 1, 2020 · 16 comments
Closed

GitHub user.email can be null #374

arunoda opened this issue Jul 1, 2020 · 16 comments
Labels
bug Something isn't working stale Did not receive any activity for 60 days

Comments

@arunoda
Copy link
Contributor

arunoda commented Jul 1, 2020

Describe the bug
GitHub user.email field does not always contains the email. It has the email, if the user decided to share the email to public.
So, session.user.email can be null in most of the cases.

To Reproduce

  • Create a simple app to with the GitHub provider
  • Disable sharing your GitHub email to public
  • If you try to login then session.user.email will be null

Expected behavior
We should expose the email in session.user

Additional context
In order to get the email, we need to add user.email scope to GitHub. We need to fetch emails using an API endpoint.

I read the GitHub provider file and I'm not sure we can apply login in the profile creating function.
What's the best way to implement the above logic?

@arunoda arunoda added the bug Something isn't working label Jul 1, 2020
@iaincollins iaincollins added the stale Did not receive any activity for 60 days label Jul 1, 2020
@iaincollins
Copy link
Member

This is not an error it is mentioned in the documentation.

GitHub allows the user not to expose their email address to OAuth services if they have relevant privacy settings are enabled.

You can modify the scope property on any provider - though we are already requesting it implicitly via the user property (see GitHub OAuth docs for details on scope options) so in this case will not make any difference.

@arunoda
Copy link
Contributor Author

arunoda commented Jul 1, 2020

@iaincollins this is certainly is a bug.

Let me clarify why?

  • We normally use an email to create a user account of refer a user. (Let me know, if that's not the case)
  • Due to this issue, this is introduce bugs when someone want to use this in a real app

GitHub allows the user not to expose their email address to OAuth services if they have relevant privacy settings are enabled.

This is not true. With the user scope, they do not expose the email.
But with user.email it does.

It's about using the correct scope and user will decide to give the email or not.

@iaincollins
Copy link
Member

iaincollins commented Jul 1, 2020

@arunoda The GitHub documentation I linked to above disagrees.

Screenshot 2020-07-01 at 10 29 23

Screenshot 2020-07-01 at 10 48 01

To clarify, you are not guaranteed to get an email address with all OAuth providers.

If you want to require one, you can use a custom signin() callback to enforce this.

@aslakhellesoy
Copy link

aslakhellesoy commented Jul 16, 2020

@arunoda the default user scope is sufficient as @iaincollins pointed out. You need to request the user's emails in a custom signin callback as they are never included in the metadata returned by github:

  callbacks: {
    signin: async (profile, account, metadata) => {
      // https://developer.github.com/v3/users/emails/#list-email-addresses-for-the-authenticated-user
      const res = await fetch('https://api.github.com/user/emails', {
        headers: {
          'Authorization': `token ${account.accessToken}`
        }
      })
      const emails = await res.json()
      if (!emails || emails.length === 0) {
        return
      }
      // Sort by primary email - the user may have several emails, but only one of them will be primary
      const sortedEmails = emails.sort((a, b) => b.primary - a.primary)
      profile.email = sortedEmails[0].email
    },
  },

Since fetch runs on the server, you have to use a Node.js implementation, for example node-fetch.

Hope this helps

@arunoda
Copy link
Contributor Author

arunoda commented Jul 23, 2020

Thanks @aslakhellesoy this callbacks is what I was looking for.

@uinstinct
Copy link

@aslakhellesoy can you please with the full example

import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'


const options = {
    providers: [
        Providers.Google({
            clientId: process.env.FRONT_GOOGLE_CLIENT_ID,
            clientSecret: process.env.FRONT_GOOGLE_CLIENT_SECRET
        }),
        Providers.GitHub({
            clientId: process.env.FRONT_GITHUB_CLIENT_ID,
            clientSecret: process.env.FRONT_GITHUB_CLIENT_SECRET
        }),
    ],
    database: process.env.FRONT_DB_URL,

    secret: process.env.FRONT_SESSION_SECRET,

    session: {
        jwt: true,
    },

    jwt: {

        secret: process.env.FRONT_JWT_SECRET,

    },

    pages: {
    },

    callbacks: {
        signin: async (profile, account, metadata) => {
            console.info('we are here to see the callback\nP\nP');
            console.log(profile, 'is the profile');
            console.log(account, 'is the account');
            console.log(metadata, 'is the metadata');
            const res = await fetch('https://api.github.com/user/emails', {
                headers: {
                    'Authorization': `token ${account.accessToken}`
                }
            })
            const emails = await res.json()
            if (!emails || emails.length === 0) {
                return
            }
            const sortedEmails = emails.sort((a, b) => b.primary - a.primary)
            profile.email = sortedEmails[0].email
        },
    },

    events: {},

    debug: process.env.NODE_ENV === 'development',
}

export default (req, res) => NextAuth(req, res, options)

I tried to use this code but nothing happenend.
Can you please explain me what to do?
Where should I put your code?

@uinstinct
Copy link

I have fixed it:
It should be* (Note the cases of signIn)

callbacks:{
  signIn
}

instead of

callbacks:{
  signin

@verekia
Copy link

verekia commented Sep 30, 2021

@aslakhellesoy Thank you so much for this. I was getting a null email until I added your callback. I'm using user:email.

You need to request the user's emails in a custom signin callback as they are never included in the metadata returned by github

Shouldn't that be mentioned in the docs? If that's always the case that seems like a pretty big deal that users created via GitHub by Next Auth never have an associated email by default.

Here is my slightly more compact version that is TypeScript-friendly, and checks for GitHub (Next Auth v3):

signIn: async (profile, account) => {
  if (account.provider === 'github') {
    const res = await fetch('https://api.github.com/user/emails', {
      headers: { Authorization: `token ${account.accessToken}` },
    })
    const emails = await res.json()
    if (emails?.length > 0) {
      profile.email = emails.sort((a, b) => b.primary - a.primary)[0].email
    }
    return true
  }
},

@balazsorban44
Copy link
Member

next-auth v4 will include email by default. In fact, we aim for returning exactly the same fields for ALL our providers by default. See #2524

@abdofola
Copy link

abdofola commented Jul 21, 2022

In order to access the pbulic email addresses, the url string should be https://api.github.com/user/public_emails, as in the docs so the callbacks object would look like this:

callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      const res = await fetch("https://api.github.com/user/public_emails", {
        headers: {
          Accept: "application/vnd.github+json",
          Authorization: `token ${account.access_token}`,
        },
      });
     
    },
  }, 

@pauxiel
Copy link

pauxiel commented Jul 21, 2022

import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import GithubProvider from 'next-auth/providers/github'
import MailchimpProvider from 'next-auth/providers/mailchimp'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import prisma from '../../../lib/prisma'

export const authOptions = {
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,

      profile(profile) {
        return {
          id: profile.id.toString(),
          name: profile.name || profile.login,
          username: profile.login,
          email: profile.email,

          image: profile.avatar_url,
        }
      },
    }),

    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,

      profile(profile) {
        return {
          id: profile.sub,
          name: profile.name || profile.login,
          username: profile.login,
          email: profile.email,
          image: profile.avatar_url,
        }
      },
    }),

    MailchimpProvider({
      clientId: process.env.MAILCHIMP_CLIENT_ID,
      clientSecret: process.env.MAILCHIMP_CLIENT_SECRET,
      profile(profile) {
        return {
          id: profile.id.toString(),
          name: profile.name || profile.login,
          username: profile.login,
          email: profile.email,
          image: profile.avatar_url,
        }
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  adapter: PrismaAdapter(prisma),
  callbacks: {
    session: ({ session, user }) => ({
      ...session,
      user: {
        ...session.user,
        id: user.id,
        // email: user.email,
        username: user.username,
      },
    }),
  },

  //get github email
}

export default NextAuth(authOptions)


i follow along a tutorial and got this code. it is in my api/auth/[...nextauth] , i have tried adding the what i read here to make the github sign in return the user email. but it is not working.

this is the index page

// import { NextPage } from 'next'
import { useSession, signOut, getSession } from 'next-auth/react'
import { NextPage } from 'next'
import useRequireAuth from '../lib/useRequireAuth'

export async function getServerSideProps(context) {
  const session = await getSession(context)

  if (!session) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    }
  }

  return {
    props: {
      session,
    },
  }
}

const Home: NextPage = () => {
  const { data: session } = useSession()

  return (
    <>
      <h1>{`Welcome ${session?.user?.name}`}</h1>
      <p>{`Welcome ${session?.user?.email}`}</p>
      <button onClick={() => signOut()}>sign out</button>
    </>
  )
}

export default Home


it keeps returning null as user email.

here is the login page


import { getSession, signIn } from 'next-auth/react'
import useRequireAuth from '../lib/useRequireAuth'
import {
  Box,
  VStack,
  Button,
  chakra,
  FormLabel,
  Input,
  Flex,
  Image,
  Stack,
  SimpleGrid,
  Badge,
  VisuallyHidden,
  InputGroup,
  InputRightElement,
} from '@chakra-ui/react'
import { BsGithub } from 'react-icons/bs'
import { FcGoogle } from 'react-icons/fc'
import { useState } from 'react'

export async function getServerSideProps(context) {
  const session = await getSession(context)

  if (session) {
    return {
      redirect: {
        destination: '/',
        permanent: false,
      },
    }
  }

  return {
    props: { session },
  }
}

const providers = [
  { name: 'github', Icon: BsGithub },
  { name: 'google', Icon: FcGoogle },
]

function Login() {
  const [email, setEmail] = useState<string>('')



  const handleSubmit = (e) => {
    e.preventDefault()
    if (!email) return false

    signIn('email', { email, redirect: false })
  }
  return (

 <VStack w="full">
          {providers.map(({ name, Icon }) => (
            <Button
              key={name}
              leftIcon={<Icon />}
              w="100%"
              onClick={() => signIn(name)}
            >
              Sign in with {name}
            </Button>
          ))}
        </VStack>

)

export default Login

please i will appreciate your help on how i can solve this

https://github.com/pauxiel/circle/tree/onboarded-features

@AlessandroVol23
Copy link

I still face this issue even that it seems to be implemented and supported. But for users with an email address set to private I still face this issue

@IRules
Copy link

IRules commented Jul 11, 2023

I still face this issue even that it seems to be implemented and supported. But for users with an email address set to private I still face this issue

Hey, I faced this issue because I had a Github App instead of a Github OAuth App. See if that is the case for you too, hope it helps!

@bcjordan
Copy link

Is there any support for GitHub Apps + NextAuth? Would love to handle the fine-grained repository scopes it has with this (and it also supports multiple callback URLs so you can develop localhost with same app).

@mortocks
Copy link

mortocks commented Oct 4, 2023

Hey, I faced this issue because I had a Github App instead of a Github OAuth App. See if that is the case for you too, hope it helps!

Agree with @IRules . It doesn't help that the next-auth docs link you to the Github Apps page rather than the oAuth apps

@thomasmol
Copy link

thomasmol commented Jan 5, 2024

I think this can be fixed for GitHub apps by adding permissions to your github app: https://github.com/settings/apps/[YOUR GITHUB APP NAME]/permissions , there you can add a permission for reading user emails. By default, the setting is set to 'no access'.
Screenshot 2024-01-05 at 14 40 39

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working stale Did not receive any activity for 60 days
Projects
None yet
Development

No branches or pull requests