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

Yandex provider + Adaptor issue (invalid_grant(code has expired)) #11611

Open
KhekhaevSalekh opened this issue Aug 15, 2024 · 6 comments
Open
Labels
bug Something isn't working providers triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@KhekhaevSalekh
Copy link

Provider type

Yandex

Environment

  System:
    OS: Windows 10 10.0.19045
    CPU: (8) x64 AMD Ryzen 5 1400 Quad-Core Processor
    Memory: 11.94 GB / 23.93 GB
  Binaries:
    Node: 20.16.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.21 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 10.8.1 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (127.0.2651.74)
    Internet Explorer: 11.0.19041.4355
  npmPackages:
    @auth/core: ^0.34.2 => 0.34.2
    @auth/hasura-adapter: ^1.4.2 => 1.4.2
    next: ^14.1.0 => 14.1.0
    next-auth: ^4.24.7 => 4.24.7
    react: ^18 => 18.3.1

Reproduction URL

https://github.com/KhekhaevSalekh/next-auth-Yandex-issue

Describe the issue

When I use Yandex provider with next-auth all goes well. But when I connect hasura adapter i get error.

The error I get is (invalid_grant(code has expired)).
When a new user presses "Sign in" by Yandex he does not go to home page. He appears at sign-in page. Also there is not next-auth.session-token coockie.
In the same time all necessary information is written in the database.
When he presses "Sign in" by Yandex second time he is on the home page.

With Google and GitHub authorization everything is good.
I have checked this behavior on vercel and locally.

How to reproduce

Just start project from Reproduction URL and try to sighin with yandex.

Expected behavior

Normal sign in flow without this error.

@KhekhaevSalekh KhekhaevSalekh added bug Something isn't working providers triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Aug 15, 2024
@degibons
Copy link

degibons commented Aug 19, 2024

The same problem with Yandex provider.

Terminal output:

GET /api/auth/callback/yandex?code=9756967&cid=47kjwvgdk0fxmxmfvuzyd46v2r 200 in 2886ms
error { error: 'invalid_grant', error_description: 'Code has expired' }

[auth][debug]: callback route error details {
  "method": "GET",
  "query": {
    "code": "9756967",
    "cid": "47kjwvgdk0fxmxmfvuzyd46v2r"
  }
}
[auth][error] CallbackRouteError: Read more at https://errors.authjs.dev#callbackrouteerror
[auth][cause]: Error: TODO: Handle OAuth 2.0 response body error
    at handleOAuth (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/actions/callback/oauth/callback.js:112:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
...

Also, sometimes there is an error with PKCE verification, but authorization occurs. Quite unstable provider.

[auth][error] InvalidCheck: PKCE code_verifier cookie was missing. Read more at https://errors.authjs.dev#invalidcheck
    at Object.use (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/actions/callback/oauth/checks.js:57:19)

Environment

OS: Windows 10
Node: 20.11
Chrome 127.0.6533.120
next-auth: 5.0.0-beta.20
@auth/prisma-adapter: 2.4.2
next: 14.2.5

@KhekhaevSalekh
Copy link
Author

The same problem with Yandex provider.

Terminal output:

GET /api/auth/callback/yandex?code=9756967&cid=47kjwvgdk0fxmxmfvuzyd46v2r 200 in 2886ms
error { error: 'invalid_grant', error_description: 'Code has expired' }

[auth][debug]: callback route error details {
  "method": "GET",
  "query": {
    "code": "9756967",
    "cid": "47kjwvgdk0fxmxmfvuzyd46v2r"
  }
}
[auth][error] CallbackRouteError: Read more at https://errors.authjs.dev#callbackrouteerror
[auth][cause]: Error: TODO: Handle OAuth 2.0 response body error
    at handleOAuth (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/actions/callback/oauth/callback.js:112:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
...

Also, sometimes there is an error with PKCE verification, but authorization occurs. Quite unstable provider.

[auth][error] InvalidCheck: PKCE code_verifier cookie was missing. Read more at https://errors.authjs.dev#invalidcheck
    at Object.use (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/actions/callback/oauth/checks.js:57:19)

Environment

OS: Windows 10
Node: 20.11
Chrome 127.0.6533.120
next-auth: 5.0.0-beta.20
@auth/prisma-adapter: 2.4.2
next: 14.2.5

I spent about a week troubleshooting this error, and the only workable solution I’ve found so far involves opening a second tab. However, I'm not satisfied with this workaround.

It would be great if the next-auth developers could consider supporting an OAuth flow similar to the one used by the Yandex provider.

The issue originates from the line in the signIn function from next-auth/react/index.js:

window.location.href = url.

To investigate further, you can insert a process.exit() command right before this line and log the URL for different providers (I tested with Google, GitHub, and Yandex). Then, in your browser's console, manually click on the URI. The behavior for Yandex differs from the others. It likely needs something like

window.open(url).

Regarding your second issue related to PKCE, you shouldn’t encounter this error if you haven't modified the default settings for the Yandex provider. Yandex defaults to using checks='state', so perhaps this parameter was changed (by you). Although I haven't extensively used it, even with the checks=['state', 'pkce'] setting, I didn't experience the same error.

@KhekhaevSalekh
Copy link
Author

I will continue to add different approachs I found in the repository which is attached to this issue (reproduction url). I hope that next-auth developers will answer. Maybe I missed something and the error can be solved straightforward.

@KhekhaevSalekh
Copy link
Author

KhekhaevSalekh commented Aug 20, 2024

The first approach is not acceptable (second tab) because it looks strange relative to the Google or GitHub. I will write second approach. I would like to read your thoughts. Is it acceptable approach? For my local and prod this works fine for now.

  1. In Yandex Oauth API console for REDIRECT_URI we use something like this http://localhost:3000/api/customYandex.
  2. In next-auth options configuration file for Yandex provider we use:
Yandex({
      clientId: process.env.YANDEX_CLIENT_ID!,
      clientSecret: process.env.YANDEX_CLIENT_SECRET!, 
      authorization:{ 
        params:{ 
          redirect_uri:`${process.env.NEXTAUTH_URL}/api/customYandex`
        },  
      },
      allowDangerousEmailAccountLinking: true,  
      httpOptions: {
        timeout: 10000,
      },
    }),

In openIDClient library there is by default written 10 sec. So maybe there is no need to write timeout: 10000. But when I saw this code for first time this parameter was written already. So I decide to not change this.

  1. If in this api we write
import {NextRequest, NextResponse } from "next/server";
import { cookies } from 'next/headers';

export async function GET(req : NextRequest) {
  const url = new URL(req.url);
  console.log('URL', url)
  const code = url.searchParams.get('code');
  const cid = url.searchParams.get('cid');
  const state = url.searchParams.get('state');
  const redirectUrl = ${process.env.NEXTAUTH_URL}/api/auth/callback/yandex?code=${code}&cid=${cid}&state=${state};
  return NextResponse.redirect(redirectUrl);
}

we get two consols and the error `invalid_grant (code has expired).

  1. So I change this code like this:
import {NextRequest, NextResponse } from "next/server";
import { cookies } from 'next/headers';

export async function GET(req : NextRequest) {
  const url = new URL(req.url);

  const code = url.searchParams.get('code');
  const cid = url.searchParams.get('cid');
  const state = url.searchParams.get('state');

  const cookieStore = cookies();
  const firstRequestReceived = cookieStore.get('yandex_oauth_first_received');

  if (!firstRequestReceived) {
    cookieStore.set('yandex_oauth_first_received', 'true', {
      maxAge: 15, 
      path: '/api/customYandex', 
    });
    return new Response(null, { status: 204 }); 
  }
  cookieStore.set('yandex_oauth_first_received', 'false', {
    maxAge: -1, 
    path: '/api/customYandex',
  });
  const redirectUrl = ${process.env.NEXTAUTH_URL}/api/auth/callback/yandex?code=${code}&cid=${cid}&state=${state};
  return NextResponse.redirect(redirectUrl);
}

What do you think about it? What problem can arise from this approach?

@KhekhaevSalekh
Copy link
Author

KhekhaevSalekh commented Aug 21, 2024

The approach above works. But 1 of 20 attempts failed. I noted that it is enough to set timer like this:

import { NextResponse } from "next/server";

export async function GET(req) {
  const url = new URL(req.url);
  const code = url.searchParams.get('code');
  const cid = url.searchParams.get('cid');

  await new Promise(resolve => setTimeout(resolve, 5000));

  const redirectUrl = `http://localhost:3000/api/auth/callback/yandex?code=${code}&cid=${cid}`;
  return NextResponse.redirect(redirectUrl);
}

@degibons
Copy link

degibons commented Sep 13, 2024

The approach above works. But 1 of 20 attempts failed. I noted that it is enough to set timer like this:

import { NextResponse } from "next/server";

export async function GET(req) {
  const url = new URL(req.url);
  const code = url.searchParams.get('code');
  const cid = url.searchParams.get('cid');

  await new Promise(resolve => setTimeout(resolve, 5000));

  const redirectUrl = `http://localhost:3000/api/auth/callback/yandex?code=${code}&cid=${cid}`;
  return NextResponse.redirect(redirectUrl);
}

Thanks for this workaround, it's worked for me. But I noticed that the described problem only occurs when the app starts "cold" (e.g. right after I run next dev and there is no cache for components). Experimentally, I found out that a 3 seconds timeout is enough for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working providers triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

No branches or pull requests

2 participants