[GH-ISSUE #4327] Generic OAuth - How to use a GET callback endpoint instead of POST? #27221

Closed
opened 2026-04-17 18:07:11 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @adeeb1 on GitHub (Aug 31, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/4327

I'm implementing OAuth2 using the Generic OAuth plugin, and it is working well when initiated from a sign in button on my website.

However, I also need to support initiating OAuth from a partner's website, and it seems I need to change my callback endpoint from a POST to a GET to support this. The partner has their own portal with an icon for my website's app.

When my app's icon is clicked on the portal, it needs to log the user into my website with OAuth. Currently, I am setting the URL for the icon to the default OAuth callback URL, which in my case is ${baseURL}/api/auth/oauth2/callback/classlink. When the user clicks on the icon now, I receive a state_not_found error, as seen below. It seems this is because it sends a GET request to my callback URL without the expected parameters that it normally sends using a POST request when initiated from my website's sign in button.

Image

I've shared my implementation below.

Frontend code

await authClient.signIn.oauth2({
  providerId: 'classlink',
  callbackURL: BASE_URL,
  fetchOptions: {
    onSuccess: async () => {
      handlePostSignIn()
    },
  }
})

Backend code

const auth = betterAuth({
  ...
  plugins: [
    ...
    genericOAuth({
      config: [
        {
          providerId: 'classlink',
          clientId: process.env.CLASSLINK_CLIENT_ID as string,
          clientSecret: process.env.CLASSLINK_CLIENT_SECRET as string,
          scopes: ['full', 'profile', 'openid'],
          discoveryUrl: 'https://launchpad.classlink.com/.well-known/openid-configuration',
          disableSignUp: true,
        },
      ],
    })
  ]
  ...
})
  1. Is the callback URL the correct one to use in this case? If not, which URL should I use?
  2. Is there a way to set the callback endpoint to a GET instead of a POST? I read that I can create a custom endpoint but would prefer to avoid doing that if possible.

Thanks in advance!

Originally created by @adeeb1 on GitHub (Aug 31, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/4327 I'm implementing OAuth2 using the [Generic OAuth](https://www.better-auth.com/docs/plugins/generic-oauth) plugin, and it is working well when initiated from a sign in button on my website. However, I also need to support initiating OAuth from a partner's website, and it seems I need to change my callback endpoint from a POST to a GET to support this. The partner has their own portal with an icon for my website's app. When my app's icon is clicked on the portal, it needs to log the user into my website with OAuth. Currently, I am setting the URL for the icon to the default OAuth callback URL, which in my case is `${baseURL}/api/auth/oauth2/callback/classlink`. When the user clicks on the icon now, I receive a `state_not_found` error, as seen below. It seems this is because it sends a GET request to my callback URL without the expected parameters that it normally sends using a POST request when initiated from my website's sign in button. <img width="357" height="304" alt="Image" src="https://github.com/user-attachments/assets/b11fd113-b57e-4b22-9e6e-c35ab245975c" /> I've shared my implementation below. **Frontend code** ``` await authClient.signIn.oauth2({ providerId: 'classlink', callbackURL: BASE_URL, fetchOptions: { onSuccess: async () => { handlePostSignIn() }, } }) ``` **Backend code** ``` const auth = betterAuth({ ... plugins: [ ... genericOAuth({ config: [ { providerId: 'classlink', clientId: process.env.CLASSLINK_CLIENT_ID as string, clientSecret: process.env.CLASSLINK_CLIENT_SECRET as string, scopes: ['full', 'profile', 'openid'], discoveryUrl: 'https://launchpad.classlink.com/.well-known/openid-configuration', disableSignUp: true, }, ], }) ] ... }) ``` 1. Is the callback URL the correct one to use in this case? If not, which URL should I use? 2. Is there a way to set the callback endpoint to a GET instead of a POST? I read that I can [create a custom endpoint](https://www.better-auth.com/docs/concepts/plugins#endpoints) but would prefer to avoid doing that if possible. Thanks in advance!
GiteaMirror added the locked label 2026-04-17 18:07:11 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 31, 2025):

Hi @adeeb1! I'm Dosu and I’m helping the better-auth team.

The Generic OAuth plugin's callback endpoint only supports GET requests, which matches the standard OAuth2 flow—there's no way to configure it to accept POST requests, and this isn't needed for typical provider integrations (source).

The state_not_found error happens because when the OAuth flow is started from your own site, Better Auth generates and stores a state value in the user's session. If the flow is initiated from a partner portal (by directly linking to your callback endpoint), that state/session doesn't exist, so the callback can't validate the request (source).

To support OAuth login from a partner's portal, you need to ensure the flow starts from your site, not by linking directly to the callback endpoint. Typically, the partner should link to a user-facing route on your site (like /login), which then triggers the OAuth flow using your frontend code. Directly linking to the callback endpoint will always fail with state_not_found due to missing session state.

If the partner can't change their integration, a custom flow or endpoint may be required to handle this use case, but that's not supported out of the box.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3239816672 --> @dosubot[bot] commented on GitHub (Aug 31, 2025): <!-- Greeting --> Hi @adeeb1! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The Generic OAuth plugin's callback endpoint only supports GET requests, which matches the standard OAuth2 flow—there's no way to configure it to accept POST requests, and this isn't needed for typical provider integrations [(source)](https://github.com/better-auth/better-auth/blob/cf7f2f118147b6a79a99fcf48898408f038390f5/packages/better-auth/src/plugins/generic-oauth/index.ts). The `state_not_found` error happens because when the OAuth flow is started from your own site, Better Auth generates and stores a `state` value in the user's session. If the flow is initiated from a partner portal (by directly linking to your callback endpoint), that state/session doesn't exist, so the callback can't validate the request [(source)](https://github.com/better-auth/better-auth/issues/3488). To support OAuth login from a partner's portal, you need to ensure the flow starts from your site, not by linking directly to the callback endpoint. Typically, the partner should link to a user-facing route on your site (like `/login`), which then triggers the OAuth flow using your frontend code. Directly linking to the callback endpoint will always fail with `state_not_found` due to missing session state. If the partner can't change their integration, a custom flow or endpoint may be required to handle this use case, but that's not supported out of the box. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/5a2c35e1-e3dc-4c59-8bba-916572e092e0?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/5a2c35e1-e3dc-4c59-8bba-916572e092e0?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/5a2c35e1-e3dc-4c59-8bba-916572e092e0?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/5a2c35e1-e3dc-4c59-8bba-916572e092e0?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/5a2c35e1-e3dc-4c59-8bba-916572e092e0?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/5a2c35e1-e3dc-4c59-8bba-916572e092e0?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/5a2c35e1-e3dc-4c59-8bba-916572e092e0?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4327)
Author
Owner

@adeeb1 commented on GitHub (Aug 31, 2025):

Thank you to DosuBot! I was able to solve this by adding a dedicated page for logging in with our OAuth partner, Classlink.

Before
I had a /signin page on my frontend with buttons to initiate OAuth.

After
I added a /signin/classlink page to my Next.js app, and I initiate OAuth with authClient.signIn.oauth2({ ... }) in a useEffect hook:

const ClasslinkSignIn = () => {
  useEffect(() => {
    const signIn = async () => {
        await authClient.signIn.oauth2({
          providerId: 'classlink',
          callbackURL: BASE_URL,
        })
    }

    signIn()
  }, [])

  return (
    <div>Signing in...</div>
  )
}

Now I have a dedicated path for initiating SSO. I've changed the URL on my app icon in the partner portal to {BASE_URL}/signin/classlink so users can sign in with a single click.

<!-- gh-comment-id:3239882266 --> @adeeb1 commented on GitHub (Aug 31, 2025): Thank you to DosuBot! I was able to solve this by adding a dedicated page for logging in with our OAuth partner, Classlink. **Before** I had a `/signin` page on my frontend with buttons to initiate OAuth. **After** I added a `/signin/classlink` page to my Next.js app, and I initiate OAuth with `authClient.signIn.oauth2({ ... })` in a useEffect hook: ``` const ClasslinkSignIn = () => { useEffect(() => { const signIn = async () => { await authClient.signIn.oauth2({ providerId: 'classlink', callbackURL: BASE_URL, }) } signIn() }, []) return ( <div>Signing in...</div> ) } ``` Now I have a dedicated path for initiating SSO. I've changed the URL on my app icon in the partner portal to `{BASE_URL}/signin/classlink` so users can sign in with a single click.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27221