nextauthjs / next-auth

Authentication for the Web.
https://authjs.dev
ISC License
24.07k stars 3.33k forks source link

Twitch Provider missing client id #6363

Open brazelja opened 1 year ago

brazelja commented 1 year ago

Provider type

Twitch

Environment

System:

OS: Linux 5.15 Ubuntu 22.04.1 LTS 22.04.1 LTS (Jammy Jellyfish) CPU: (16) x64 AMD Ryzen 9 5900HX with Radeon Graphics Memory: 10.68 GB / 15.34 GB Container: Yes Shell: 5.8.1 - /usr/bin/zsh

Binaries:

Node: 18.12.1 - ~/.volta/tools/image/node/18.12.1/bin/node Yarn: 1.22.19 - ~/.volta/tools/image/yarn/1.22.19/bin/yarn npm: 8.19.3 - ~/.volta/tools/image/npm/8.19.3/bin/npm pnpm: 6.11.0 - ~/.volta/bin/pnpm

Packages:

@auth/core: 0.2.5 @auth/sveltekit: 0.1.12

Reproduction URL

https://authjs.dev/reference/oauth-providers/twitch

Describe the issue

I am using @auth/sveltekit and attempting to add Twitch and Discord login options. I followed the documentation pages for each and have them set up like so:

import { SvelteKitAuth } from '@auth/sveltekit';
import Discord from '@auth/core/providers/discord';
import Twitch from '@auth/core/providers/twitch';
import {
  DISCORD_CLIENT_ID,
  DISCORD_CLIENT_SECRET,
  TWITCH_CLIENT_ID,
  TWITCH_CLIENT_SECRET
} from '$env/static/private';

export const handle = SvelteKitAuth({
  providers: [
    Discord({
      clientId: DISCORD_CLIENT_ID,
      clientSecret: DISCORD_CLIENT_SECRET
    }),
    Twitch({
      clientId: TWITCH_CLIENT_ID,
      clientSecret: TWITCH_CLIENT_SECRET
    })
  ]
}

Using the demo project setup described [here[(https://authjs.dev/reference/sveltekit/modules/main) but replacing GitHub with Twitch/Discord I was able to get the project up and running. Logging in with Discord works perfectly fine, but when I log attempt to log in with Twitch I get:

CallbackRouteError: "response" is not a conform Token Endpoint response
    at Module.callback (file:///home/<redacted>/node_modules/.pnpm/@auth+core@0.2.5/node_modules/@auth/core/lib/routes/callback.js:244:23)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async AuthInternal (file:///home/<redacted>/node_modules/.pnpm/@auth+core@0.2.5/node_modules/@auth/core/lib/index.js:52:38)
    at async Proxy.Auth (file:///home/<redacted>/node_modules/.pnpm/@auth+core@0.2.5/node_modules/@auth/core/index.js:95:30)
    at async respond (file:///home/<redacted>/node_modules/.pnpm/@sveltejs+kit@1.0.10_svelte@3.55.0+vite@4.0.4/node_modules/@sveltejs/kit/src/runtime/server/index.js:229:20)
    at async file:///home/<redacted>/node_modules/.pnpm/@sveltejs+kit@1.0.10_svelte@3.55.0+vite@4.0.4/node_modules/@sveltejs/kit/src/exports/vite/dev/index.js:458:22
[auth][error][CallbackRouteError]: Read more at https://errors.authjs.dev#callbackrouteerror
{
  "provider": "twitch"
}

This is despite using the Provider as is and not manipulating it in any way. Digging deeper into the @auth/core code I narrowed down the error to https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/lib/oauth/callback.ts#L107. Logging out the response of the authorizationCodeGrantRequest call shows { status: 400, message: 'missing client id' }, despite client_id being present when passed to the Twitch provider and confirmed by logging out client object that is passed to authorizationCodeGrantRequest, which was in the format:

{
  client_id: '<redacted-client-id-value>',
  client_secret: '<redacted-client-secret-value>'
}

This error completely prevents users of my application from logging in with Twitch.

How to reproduce

Steps to reproduce are described above.

Roughly:

import { SvelteKitAuth } from '@auth/sveltekit';
import Twitch from '@auth/core/providers/twitch';
import { TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET } from '$env/static/private';

export const handle = SvelteKitAuth({
  providers: [
    Twitch({
      clientId: TWITCH_CLIENT_ID,
      clientSecret: TWITCH_CLIENT_SECRET
    })
  ]
}

Expected behavior

Ideally the Twitch provider should let users authenticate with an application via Twitch without error.

balazsorban44 commented 1 year ago

Thanks, I looked into this. The reason it was a bit cryptic is that Twitch does not conform to the error response spec defined at: https://www.rfc-editor.org/rfc/rfc6749#section-5.2

It should return error and optionally error_description, not {status: 400, message: string}.

So we could not detect the exact issue to show you.

I also noticed at https://id.twitch.tv/oauth2/.well-known/openid-configuration that Twitch only supports client_secret_post as token_endpoint_auth_method, and the default is client_secret_basic, hence it did not recognize the client_id being sent.

The fix is easy, we need to add client: { token_endpoint_auth_method: "client_secret_post" } to the default Twitch config (you can do this in your code for now to overcome the issue).

~I'll investigate if we could automatically detect if a provider only supports this method and use that instead, by reading the discovery endpoint data.~ Not a good idea after all, configuring per-provider is the way to do it.

Fixing the above raises a new issue though, as Twitch also makes a second mistake by returning scope as an array (scope: [ 'openid', 'user:read:email' ], based on the default config https://github.com/nextauthjs/next-auth/blob/4056dafa7a9a4542b6c86e044cd96c4d7655503c/packages/core/src/providers/twitch.ts#L20) in the successful response, which again does not conform to the spec. (See 5.1 Successful Response, 3.3 Access Token Scope).

This requires special handling for Twitch, which is unfortunate, and would like to avoid it at all costs. Ideally, we could force Twitch to fix this on their side, I'll try my best to reach out and see what happens.

brazelja commented 1 year ago

Thanks for the swift reply. I'll test out your recommendation this evening and post back here the results.

brazelja commented 1 year ago

Confirmed that client: { token_endpoint_auth_method: "client_secret_post" } progresses you past the "Missing client id" message to then hit the scope format error.

Caca7787878 commented 5 months ago

No