[GH-ISSUE #6958] MCP redirect bug with magic link auth #19315

Closed
opened 2026-04-15 18:14:10 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @amirilovic on GitHub (Dec 23, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6958

Originally assigned to: @bytaesu on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Reproduction steps can be found here: https://github.com/amirilovic/better-auth-magic-link-mcp-issue

Current vs. Expected behavior

Expected behavior

When auth flow is started by mcp client, redirect back to the client should happen after successful login.

Current behavior

User is redirected to /api/auth/error?error=invalid_client

What version of Better Auth are you using?

1.4.8

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 24.5.0: Tue Apr 22 19:53:27 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6041",
    "release": "24.5.0",
    "cpuCount": 14,
    "cpuModel": "Apple M4 Pro",
    "totalMemory": "48.00 GB",
    "freeMemory": "0.19 GB"
  },
  "node": {
    "version": "v22.21.1",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.4"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^15.1.0"
    },
    {
      "name": "react",
      "version": "^19.0.0"
    }
  ],
  "databases": [
    {
      "name": "better-sqlite3",
      "version": "^12.0.0"
    }
  ],
  "betterAuth": {
    "version": "1.4.8",
    "config": {
      "database": {
        "name": "./auth.db",
        "open": true,
        "inTransaction": false,
        "readonly": false,
        "memory": false
      },
      "emailAndPassword": {
        "enabled": false
      },
      "plugins": [
        {
          "name": "magic-link",
          "config": {
            "id": "magic-link",
            "endpoints": {},
            "rateLimit": [
              {
                "window": 60,
                "max": 5
              }
            ],
            "options": {}
          }
        },
        {
          "name": "mcp",
          "config": {
            "id": "mcp",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "schema": {
              "oauthApplication": {
                "modelName": "oauthApplication",
                "fields": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string",
                    "required": false
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "clientId": {
                    "type": "string",
                    "unique": true
                  },
                  "clientSecret": {
                    "type": "string",
                    "required": false
                  },
                  "redirectUrls": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "disabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthAccessToken": {
                "modelName": "oauthAccessToken",
                "fields": {
                  "accessToken": {
                    "type": "string",
                    "unique": true
                  },
                  "refreshToken": {
                    "type": "string",
                    "unique": true
                  },
                  "accessTokenExpiresAt": {
                    "type": "date"
                  },
                  "refreshTokenExpiresAt": {
                    "type": "date"
                  },
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthConsent": {
                "modelName": "oauthConsent",
                "fields": {
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  },
                  "consentGiven": {
                    "type": "boolean"
                  }
                }
              }
            },
            "options": {
              "loginPage": "/sign-in"
            }
          }
        }
      ],
      "secret": "[REDACTED]",
      "trustedOrigins": [
        null
      ]
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { magicLink, mcp } from "better-auth/plugins";
import Database from "better-sqlite3";

const db = new Database("./auth.db");

export const auth = betterAuth({
  database: db,
  emailAndPassword: {
    enabled: false,
  },
  plugins: [
    magicLink({
      sendMagicLink: async ({ email, url }) => {
        // Log the magic link to console instead of sending email
        console.log("\n========================================");
        console.log("MAGIC LINK EMAIL");
        console.log("To:", email);
        console.log("Link:", url);
        console.log("========================================\n");
      },
    }),
    mcp({
      loginPage: "/sign-in",
    }),
  ],
  secret: "super-secret-key-for-testing-only",
  baseURL: process.env.APP_URL,
  trustedOrigins: [process.env.APP_URL!],
});

Additional context

Fix seems to be simple:

In packages/better-auth/src/plugins/mcp/index.ts, adds one line after clearing the cookie:

ctx.setCookie("oidc_login_prompt", "", { maxAge: 0 });

// THIS LINE - restore OAuth params from cookie to ctx.query
ctx.query = JSON.parse(cookie);

const sessionToken = ...

That code already existed and was removed here: https://github.com/better-auth/better-auth/pull/5993/changes#diff-283ee57fbb3df51da708137b618e0d34b06e90b1f1cdb3b01fd27eb662fc8bafL167

Originally created by @amirilovic on GitHub (Dec 23, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6958 Originally assigned to: @bytaesu on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Reproduction steps can be found here: https://github.com/amirilovic/better-auth-magic-link-mcp-issue ### Current vs. Expected behavior ## Expected behavior When auth flow is started by mcp client, redirect back to the client should happen after successful login. ## Current behavior User is redirected to /api/auth/error?error=invalid_client ### What version of Better Auth are you using? 1.4.8 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 24.5.0: Tue Apr 22 19:53:27 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6041", "release": "24.5.0", "cpuCount": 14, "cpuModel": "Apple M4 Pro", "totalMemory": "48.00 GB", "freeMemory": "0.19 GB" }, "node": { "version": "v22.21.1", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.4" }, "frameworks": [ { "name": "next", "version": "^15.1.0" }, { "name": "react", "version": "^19.0.0" } ], "databases": [ { "name": "better-sqlite3", "version": "^12.0.0" } ], "betterAuth": { "version": "1.4.8", "config": { "database": { "name": "./auth.db", "open": true, "inTransaction": false, "readonly": false, "memory": false }, "emailAndPassword": { "enabled": false }, "plugins": [ { "name": "magic-link", "config": { "id": "magic-link", "endpoints": {}, "rateLimit": [ { "window": 60, "max": 5 } ], "options": {} } }, { "name": "mcp", "config": { "id": "mcp", "hooks": { "after": [ {} ] }, "endpoints": {}, "schema": { "oauthApplication": { "modelName": "oauthApplication", "fields": { "name": { "type": "string" }, "icon": { "type": "string", "required": false }, "metadata": { "type": "string", "required": false }, "clientId": { "type": "string", "unique": true }, "clientSecret": { "type": "string", "required": false }, "redirectUrls": { "type": "string" }, "type": { "type": "string" }, "disabled": { "type": "boolean", "required": false, "defaultValue": false }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthAccessToken": { "modelName": "oauthAccessToken", "fields": { "accessToken": { "type": "string", "unique": true }, "refreshToken": { "type": "string", "unique": true }, "accessTokenExpiresAt": { "type": "date" }, "refreshTokenExpiresAt": { "type": "date" }, "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthConsent": { "modelName": "oauthConsent", "fields": { "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" }, "consentGiven": { "type": "boolean" } } } }, "options": { "loginPage": "/sign-in" } } } ], "secret": "[REDACTED]", "trustedOrigins": [ null ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { magicLink, mcp } from "better-auth/plugins"; import Database from "better-sqlite3"; const db = new Database("./auth.db"); export const auth = betterAuth({ database: db, emailAndPassword: { enabled: false, }, plugins: [ magicLink({ sendMagicLink: async ({ email, url }) => { // Log the magic link to console instead of sending email console.log("\n========================================"); console.log("MAGIC LINK EMAIL"); console.log("To:", email); console.log("Link:", url); console.log("========================================\n"); }, }), mcp({ loginPage: "/sign-in", }), ], secret: "super-secret-key-for-testing-only", baseURL: process.env.APP_URL, trustedOrigins: [process.env.APP_URL!], }); ``` ### Additional context Fix seems to be simple: In `packages/better-auth/src/plugins/mcp/index.ts`, adds one line after clearing the cookie: ```typescript ctx.setCookie("oidc_login_prompt", "", { maxAge: 0 }); // THIS LINE - restore OAuth params from cookie to ctx.query ctx.query = JSON.parse(cookie); const sessionToken = ... ``` That code already existed and was removed here: https://github.com/better-auth/better-auth/pull/5993/changes#diff-283ee57fbb3df51da708137b618e0d34b06e90b1f1cdb3b01fd27eb662fc8bafL167
GiteaMirror added the lockedbug labels 2026-04-15 18:14:11 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 23, 2025):

It looks like you are facing a redirect issue with the MCP plugin while using magic link authentication in Better Auth. The current behavior leads to a redirect to /api/auth/error?error=invalid_client, indicating that the original OAuth query parameters are not being restored properly after the login prompt.

To address this, you can consider the recommended fix found in related discussions: modify the MCP plugin code to restore the OAuth parameters from the oidc_login_prompt cookie back into the context query before processing the authorization. Adding the line ctx.query = JSON.parse(cookie); right after clearing the cookie can help in retaining these parameters, allowing the authorization to proceed correctly without reaching the error state [4].

If you have any further questions or need help implementing this fix, feel free to ask! What additional details can I help you with regarding your implementation?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3687342270 --> @better-auth-agent[bot] commented on GitHub (Dec 23, 2025): It looks like you are facing a redirect issue with the MCP plugin while using magic link authentication in Better Auth. The current behavior leads to a redirect to `/api/auth/error?error=invalid_client`, indicating that the original OAuth query parameters are not being restored properly after the login prompt. To address this, you can consider the recommended fix found in related discussions: modify the MCP plugin code to restore the OAuth parameters from the `oidc_login_prompt` cookie back into the context query before processing the authorization. Adding the line `ctx.query = JSON.parse(cookie);` right after clearing the cookie can help in retaining these parameters, allowing the authorization to proceed correctly without reaching the error state [[4]](https://github.com/better-auth/better-auth/issues/6905). If you have any further questions or need help implementing this fix, feel free to ask! What additional details can I help you with regarding your implementation? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@bytaesu commented on GitHub (Dec 23, 2025):

Hi @amirilovic, thanks for providing the repo. I’ll look into this.

<!-- gh-comment-id:3687638033 --> @bytaesu commented on GitHub (Dec 23, 2025): Hi @amirilovic, thanks for providing the repo. I’ll look into this.
Author
Owner

@dvanmali commented on GitHub (Dec 24, 2025):

Hi @amirilovic, wanted to let you know that we released the new OAuth Provider Plugin will replace MCP in the future and doesn't use cookies which appears to cause this issue. Hope it helps :)

<!-- gh-comment-id:3688652920 --> @dvanmali commented on GitHub (Dec 24, 2025): Hi @amirilovic, wanted to let you know that we released the new [OAuth Provider Plugin](https://www.better-auth.com/docs/plugins/oauth-provider) will replace MCP in the future and doesn't use cookies which appears to cause this issue. Hope it helps :)
Author
Owner

@bytaesu commented on GitHub (Dec 24, 2025):

Hi @amirilovic,

Could you let me know if this version resolves the issue? 🙂

npm i https://pkg.pr.new/better-auth/better-auth@6974
<!-- gh-comment-id:3688817343 --> @bytaesu commented on GitHub (Dec 24, 2025): Hi @amirilovic, Could you let me know if this version resolves the issue? 🙂 ``` npm i https://pkg.pr.new/better-auth/better-auth@6974 ```
Author
Owner

@amirilovic commented on GitHub (Dec 25, 2025):

@bytaesu just tested, works as expected, thanks for the quick fix!

<!-- gh-comment-id:3691799737 --> @amirilovic commented on GitHub (Dec 25, 2025): @bytaesu just tested, works as expected, thanks for the quick fix!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19315