[GH-ISSUE #8266] OAuth state_mismatch when secondaryStorage (Redis) is enabled #28359

Closed
opened 2026-04-17 19:47:12 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @bodadotsh on GitHub (Mar 1, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8266

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

With Redis configured as secondaryStorage, GitHub OAuth fails at the callback with error=state_mismatch.

The better-auth.state cookie is set and sent on the callback request, and the state query param matches what was sent to GitHub.

Disabling Redis (removing secondaryStorage) fixes the issue with no other changes.

So the problem appears to be in how OAuth state is stored or looked up when Redis is used, not in cookie/origin handling.

Current vs. Expected behavior

With the secondaryStorage block: callback returns 302 to /api/auth/error?error=state_mismatch.

Without it (Redis disabled): GitHub sign-in completes successfully.

What version of Better Auth are you using?

1.5.1-beta.1

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:49:24 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T8132",
    "release": "25.3.0",
    "cpuCount": 10,
    "cpuModel": "Apple M4",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.23 GB"
  },
  "node": {
    "version": "v24.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.10"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.1.6"
    },
    {
      "name": "react",
      "version": "19.2.4"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "7.4.2"
    }
  ],
  "betterAuth": {
    "version": "1.5.1-beta.1",
    "config": {
      "trustedOrigins": [
        "http://localhost:3000"
      ],
      "socialProviders": {
        "github": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "session": {
        "freshAge": 0,
        "cookieCache": {
          "enabled": true
        }
      },
      "account": {
        "accountLinking": {
          "enabled": true
        }
      },
      "onAPIError": {},
      "hooks": {},
      "plugins": [
        {
          "name": "magic-link",
          "config": {
            "id": "magic-link",
            "endpoints": {},
            "rateLimit": [
              {
                "window": 60,
                "max": 5
              }
            ],
            "options": {}
          }
        },
        {
          "name": "one-time-token",
          "config": {
            "id": "one-time-token",
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "options": {
              "expiresIn": 10
            }
          }
        },
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": {
                "code": "FAILED_TO_CREATE_USER",
                "message": "Failed to create user"
              },
              "USER_ALREADY_EXISTS": {
                "code": "USER_ALREADY_EXISTS",
                "message": "User already exists."
              },
              "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": {
                "code": "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL",
                "message": "User already exists. Use another email."
              },
              "YOU_CANNOT_BAN_YOURSELF": {
                "code": "YOU_CANNOT_BAN_YOURSELF",
                "message": "You cannot ban yourself"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE",
                "message": "You are not allowed to change users role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS",
                "message": "You are not allowed to create users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS",
                "message": "You are not allowed to list users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS",
                "message": "You are not allowed to list users sessions"
              },
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS",
                "message": "You are not allowed to ban users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS",
                "message": "You are not allowed to impersonate users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS",
                "message": "You are not allowed to revoke users sessions"
              },
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS",
                "message": "You are not allowed to delete users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD",
                "message": "You are not allowed to set users password"
              },
              "BANNED_USER": {
                "code": "BANNED_USER",
                "message": "You have been banned from this application"
              },
              "YOU_ARE_NOT_ALLOWED_TO_GET_USER": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_GET_USER",
                "message": "You are not allowed to get user"
              },
              "NO_DATA_TO_UPDATE": {
                "code": "NO_DATA_TO_UPDATE",
                "message": "No data to update"
              },
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS",
                "message": "You are not allowed to update users"
              },
              "YOU_CANNOT_REMOVE_YOURSELF": {
                "code": "YOU_CANNOT_REMOVE_YOURSELF",
                "message": "You cannot remove yourself"
              },
              "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE",
                "message": "You are not allowed to set a non-existent role value"
              },
              "YOU_CANNOT_IMPERSONATE_ADMINS": {
                "code": "YOU_CANNOT_IMPERSONATE_ADMINS",
                "message": "You cannot impersonate admins"
              },
              "INVALID_ROLE_TYPE": {
                "code": "INVALID_ROLE_TYPE",
                "message": "Invalid role type"
              }
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            }
          }
        }
      ],
      "advanced": {
        "ipAddress": {
          "ipv6Subnet": 64
        }
      }
    }
  }
}

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

Backend

Auth config (if applicable)

import { redisStorage } from "@better-auth/redis-storage"
import { redis } from "~/services/redis"

export const auth = betterAuth({
  database: prismaAdapter(db, { provider: "postgresql" }),

  // When this block is ENABLED → state_mismatch on callback
  secondaryStorage: redisStorage({
    client: redis,
    keyPrefix: "better-auth:",
  }),
  verification: {
    storeIdentifier: "hashed",
    storeInDatabase: false,
  },
  rateLimit: {
    enabled: true,
    window: 60,
    max: 100,
    storage: "secondary-storage",
  },
  
  trustedOrigins: ["http://localhost:3000"],
  socialProviders: { github: { clientId: env.AUTH_GITHUB_ID, clientSecret: env.AUTH_GITHUB_SECRET } },
  session: {
    freshAge: 0,
    cookieCache: { enabled: true },
  },
  account: { accountLinking: { enabled: true } },
  // ...
})

Additional context

No response

Originally created by @bodadotsh on GitHub (Mar 1, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8266 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce With Redis configured as `secondaryStorage`, GitHub OAuth fails at the callback with `error=state_mismatch`. The `better-auth.state` cookie is set and sent on the callback request, and the state query param matches what was sent to GitHub. Disabling Redis (removing `secondaryStorage`) fixes the issue with no other changes. So the problem appears to be in how OAuth state is stored or looked up when Redis is used, not in cookie/origin handling. ### Current vs. Expected behavior With the `secondaryStorage` block: callback returns 302 to `/api/auth/error?error=state_mismatch`. Without it (Redis disabled): GitHub sign-in completes successfully. ### What version of Better Auth are you using? 1.5.1-beta.1 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:49:24 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T8132", "release": "25.3.0", "cpuCount": 10, "cpuModel": "Apple M4", "totalMemory": "16.00 GB", "freeMemory": "0.23 GB" }, "node": { "version": "v24.14.0", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.10" }, "frameworks": [ { "name": "next", "version": "16.1.6" }, { "name": "react", "version": "19.2.4" } ], "databases": [ { "name": "@prisma/client", "version": "7.4.2" } ], "betterAuth": { "version": "1.5.1-beta.1", "config": { "trustedOrigins": [ "http://localhost:3000" ], "socialProviders": { "github": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "session": { "freshAge": 0, "cookieCache": { "enabled": true } }, "account": { "accountLinking": { "enabled": true } }, "onAPIError": {}, "hooks": {}, "plugins": [ { "name": "magic-link", "config": { "id": "magic-link", "endpoints": {}, "rateLimit": [ { "window": 60, "max": 5 } ], "options": {} } }, { "name": "one-time-token", "config": { "id": "one-time-token", "endpoints": {}, "hooks": { "after": [ {} ] }, "options": { "expiresIn": 10 } } }, { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": { "code": "FAILED_TO_CREATE_USER", "message": "Failed to create user" }, "USER_ALREADY_EXISTS": { "code": "USER_ALREADY_EXISTS", "message": "User already exists." }, "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": { "code": "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL", "message": "User already exists. Use another email." }, "YOU_CANNOT_BAN_YOURSELF": { "code": "YOU_CANNOT_BAN_YOURSELF", "message": "You cannot ban yourself" }, "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE", "message": "You are not allowed to change users role" }, "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS", "message": "You are not allowed to create users" }, "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS", "message": "You are not allowed to list users" }, "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": { "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS", "message": "You are not allowed to list users sessions" }, "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS", "message": "You are not allowed to ban users" }, "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS", "message": "You are not allowed to impersonate users" }, "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": { "code": "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS", "message": "You are not allowed to revoke users sessions" }, "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS", "message": "You are not allowed to delete users" }, "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": { "code": "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD", "message": "You are not allowed to set users password" }, "BANNED_USER": { "code": "BANNED_USER", "message": "You have been banned from this application" }, "YOU_ARE_NOT_ALLOWED_TO_GET_USER": { "code": "YOU_ARE_NOT_ALLOWED_TO_GET_USER", "message": "You are not allowed to get user" }, "NO_DATA_TO_UPDATE": { "code": "NO_DATA_TO_UPDATE", "message": "No data to update" }, "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS", "message": "You are not allowed to update users" }, "YOU_CANNOT_REMOVE_YOURSELF": { "code": "YOU_CANNOT_REMOVE_YOURSELF", "message": "You cannot remove yourself" }, "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": { "code": "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE", "message": "You are not allowed to set a non-existent role value" }, "YOU_CANNOT_IMPERSONATE_ADMINS": { "code": "YOU_CANNOT_IMPERSONATE_ADMINS", "message": "You cannot impersonate admins" }, "INVALID_ROLE_TYPE": { "code": "INVALID_ROLE_TYPE", "message": "Invalid role type" } }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } } } } ], "advanced": { "ipAddress": { "ipv6Subnet": 64 } } } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { redisStorage } from "@better-auth/redis-storage" import { redis } from "~/services/redis" export const auth = betterAuth({ database: prismaAdapter(db, { provider: "postgresql" }), // When this block is ENABLED → state_mismatch on callback secondaryStorage: redisStorage({ client: redis, keyPrefix: "better-auth:", }), verification: { storeIdentifier: "hashed", storeInDatabase: false, }, rateLimit: { enabled: true, window: 60, max: 100, storage: "secondary-storage", }, trustedOrigins: ["http://localhost:3000"], socialProviders: { github: { clientId: env.AUTH_GITHUB_ID, clientSecret: env.AUTH_GITHUB_SECRET } }, session: { freshAge: 0, cookieCache: { enabled: true }, }, account: { accountLinking: { enabled: true } }, // ... }) ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-17 19:47:12 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Mar 1, 2026):

This is a known issue with how Better Auth handles verification data when secondaryStorage is configured. When you enable Redis secondary storage, the verification table is automatically excluded from the schema, but OAuth state defaults to the "database" storage strategy. This mismatch causes the state lookup to fail during callback.

Quick fix — use cookie-based state storage instead:

export const auth = betterAuth({
  account: {
    storeStateStrategy: "cookie"  // Store OAuth state in encrypted cookies
  },
  secondaryStorage: redisStorage({
    client: redis,
    keyPrefix: "better-auth:",
  }),
  // ... rest of config
})

Alternative — keep verification data in the database:

{
  secondaryStorage: redisStorage({ /* ... */ }),
  verification: {
    storeInDatabase: true  // Force verification table to be used
  },
}

PR #8247 recently fixed verification operations for secondary-storage-only configurations, so also check if upgrading to the latest version resolves this without config changes.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3980801431 --> @dosubot[bot] commented on GitHub (Mar 1, 2026): <!-- Answer --> This is a known issue with how Better Auth handles verification data when `secondaryStorage` is configured. When you enable Redis secondary storage, the [verification table is automatically excluded from the schema](https://github.com/better-auth/better-auth/blob/bfca59f04603dd796eff31030c94b275f0d51bdf/packages/core/src/db/get-tables.ts#L290-L292), but [OAuth state defaults to the "database" storage strategy](https://github.com/better-auth/better-auth/blob/bfca59f04603dd796eff31030c94b275f0d51bdf/packages/better-auth/src/context/create-context.ts#L259-L264). This mismatch causes the state lookup to fail during callback. **Quick fix** — use cookie-based state storage instead: ```typescript export const auth = betterAuth({ account: { storeStateStrategy: "cookie" // Store OAuth state in encrypted cookies }, secondaryStorage: redisStorage({ client: redis, keyPrefix: "better-auth:", }), // ... rest of config }) ``` **Alternative** — keep verification data in the database: ```typescript { secondaryStorage: redisStorage({ /* ... */ }), verification: { storeInDatabase: true // Force verification table to be used }, } ``` [PR #8247](https://github.com/better-auth/better-auth/pull/8247) recently fixed verification operations for secondary-storage-only configurations, so also check if upgrading to the latest version resolves this without config changes. <!-- 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/5e9a845d-cc56-4979-ac1f-ddf67c4e97ad?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/5e9a845d-cc56-4979-ac1f-ddf67c4e97ad?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/5e9a845d-cc56-4979-ac1f-ddf67c4e97ad?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/5e9a845d-cc56-4979-ac1f-ddf67c4e97ad?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/5e9a845d-cc56-4979-ac1f-ddf67c4e97ad?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/5e9a845d-cc56-4979-ac1f-ddf67c4e97ad?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/5e9a845d-cc56-4979-ac1f-ddf67c4e97ad?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20a%20known%20issue%20with%20how%20Better%20Auth%20handles%20verification%20data%20when%20%60secondaryStorage%60%20is%20configured.%20When%20you%20enable%20Redis%20secondary%20storage%2C%20the%20%5Bverification%20table%20is%20automatically%20excluded%20from%20the%20schema%5D%28https%3A//github.com/better-auth/better-auth/blob/bfca59f04603dd796eff31030c94b275f0d51bdf/packages/core/src/db/get-tables.ts%23L290-L292%29%2C%20but%20%5BOAuth%20state%20defaults%20to%20the%20%22database%22%20storage%20strategy%5D%28https%3A//github.com/better-auth/better-auth/blob/bfca59f04603dd796eff31030c94b275f0d51bdf/packages/better-auth/src/context/create-context.ts%23L259-L264%29.%20This%20mismatch%20causes%20the%20state%20lookup%20to%20fail%20during%20callback.%0A%0A%2A%2AQuick%20fix%2A%2A%20%E2%80%94%20use%20cookie-based%20state%20storage%20instead%3A%0A%0A%60%60%60typescript%0Aexport%20const%20auth%20%3D%20betterAuth%28%7B%0A%20%20account%3A%20%7B%0A%20%20%20%20storeStateStrategy%3A%20%22cookie%22%20%20//%20Store%20OAuth%20state%20in%20encrypted%20cookies%0A%20%20%7D%2C%0A%20%20secondaryStorage%3A%20redisStorage%28%7B%0A%20%20%20%20client%3A%20redis%2C%0A%20%20%20%20keyPrefix%3A%20%22better-auth%3A%22%2C%0A%20%20%7D%29%2C%0A%20%20//%20...%20rest%20of%20config%0A%7D%29%0A%60%60%60%0A%0A%2A%2AAlternative%2A%2A%20%E2%80%94%20keep%20verification%20data%20in%20the%20database%3A%0A%0A%60%60%60typescript%0A%7B%0A%20%20secondaryStorage%3A%20redisStorage%28%7B%20/%2A%20...%20%2A/%20%7D%29%2C%0A%20%20verification%3A%20%7B%0A%20%20%20%20storeInDatabase%3A%20true%20%20//%20Force%20verification%20table%20to%20be%20used%0A%20%20%7D%2C%0A%7D%0A%60%60%60%0A%0A%5BPR%20%238247%5D%28https%3A//github.com/better-auth/better-auth/pull/8247%29%20recently%20fixed%20verification%20operations%20for%20secondary-storage-only%20configurations%2C%20so%20also%20check%20if%20upgrading%20to%20the%20latest%20version%20resolves%20this%20without%20config%20changes.)&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/8266)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28359