middleware icon indicating copy to clipboard operation
middleware copied to clipboard

[oauth-providers] Discord provider omits email from response

Open gc opened this issue 1 year ago • 5 comments

https://github.com/honojs/middleware/blob/cd6c667ee267f409cdf7a6a35f9cc135f5091046/packages/oauth-providers/src/providers/discord/authFlow.ts#L134

If we request the email scope, we should have response.email, but the provider only returns response.user, making us not able to retrieve the email.

gc avatar Dec 19 '24 04:12 gc

@gc Thank you for raising the issue.

Hi @monoald Can you take a look at this?

yusukebe avatar Dec 21 '24 07:12 yusukebe

Suggestion: let us get the raw response as an escape hatch. ( c.get('raw') ?)

gc avatar Dec 21 '24 20:12 gc

This took longer than I wanted, Writing this for the next person 👍

As of the current implementation of the /oauth2/@me endpoint (used in the middleware), The response is only exposing partial user object with the identify scope which doesn't retrieve the email even if the email is in scope.

https://discord.com/developers/docs/topics/oauth2#get-current-authorization-information

Response

{
   /* "application": {
        "id": "159799960412356608",
        "name": "AIRHORN SOLUTIONS",
        "icon": "f03590d3eb764081d154a66340ea7d6d",
        "description": "",
        "hook": true,
        "bot_public": true,
        "bot_require_code_grant": false,
        "verify_key": "c8cde6a3c8c6e49d86af3191287b3ce255872be1fff6dc285bdb420c06a2c3c8"
    },
    "scopes": [
        "email",
        "identify"
    ],
    "expires": "2021-01-23T02:33:17.017000+00:00",
*/
    "user": {
        "id": "268473310986240001",
        "username": "discord",
        "avatar": "f749bb0cbeeb26ef21eca719337d20f1",
        "discriminator": "0",
        "global_name": "Discord",
        "public_flags": 131072
    }
}

Younis-Ahmed avatar Feb 24 '25 09:02 Younis-Ahmed

As a workaround you can add middleware to fetch the email(add conditionals to check if email is in scope using granted-scopes) and assign it to the user's object

app.get('/discord',   
  discordAuth({
    client_id: Bun.env.DISCORD_ID,
    client_secret: Bun.env.DISCORD_SECRET,
    scope: ['identify', 'email'],
    redirect_uri: 'http://localhost:3000/discord',
}),async (c) => {
  const token = c.get('token')
  // Fetch the complete user info from Discord users endpoint.
  const userResponse = await fetch('https://discord.com/api/users/@me', {
    headers: { authorization: `Bearer ${token?.token}` },
  }).then(res => res.json())

  console.log('Raw Discord user response:', userResponse)

  const email = userResponse.email // email might be undefined if the token lacks the email scope

  if (!email) {
    throw new HTTPException(400, { message: 'Email is undefined. Make sure your OAuth2 request includes the "email" scope and the token has the proper permissions.' })
  }

  const refreshToken = c.get('refresh-token')
  const grantedScopes = c.get('granted-scopes')
  const user = c.get('user-discord') as DiscordUser & { email: string | null }
  Object.assign(user, { email })

  console.log('user', user)
  return c.json({
    token,
    refreshToken,
    grantedScopes,
    user,
  })
})

Younis-Ahmed avatar Feb 24 '25 09:02 Younis-Ahmed

Hi @yusukebe @monoald

This issue can currently be handled by using middleware at the app level. However, since the issue remains open, would you accept a PR that addresses this edge case directly at the library level?

Younis-Ahmed avatar Feb 24 '25 11:02 Younis-Ahmed