[GH-ISSUE #5397] 1.3.26 broke auth with existing API keys #27555

Closed
opened 2026-04-17 18:37:53 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @tehnrd on GitHub (Oct 18, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5397

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

This is a regression of https://github.com/better-auth/better-auth/issues/4080

Create an API key with 1.3.25, use it, it works.

Upgrade to 1.3.26, use same API key, it does not work.

Current vs. Expected behavior

API Keys created before 1.3.26 shoudl continue to work.

What version of Better Auth are you using?

1.3.27

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:45 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6000",
    "release": "25.0.0",
    "cpuCount": 10,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.22 GB"
  },
  "node": {
    "version": "v22.16.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.2"
  },
  "frameworks": [
    {
      "name": "svelte",
      "version": "^5.40.0"
    },
    {
      "name": "@sveltejs/kit",
      "version": "2.46.5"
    },
    {
      "name": "express",
      "version": "5.1.0"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.6"
    }
  ],
  "betterAuth": {
    "version": "1.3.26",
    "config": null
  }
}

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

Backend

Auth config (if applicable)


Additional context

I will try to test a 1.4 beta soon and follow up.

Originally created by @tehnrd on GitHub (Oct 18, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5397 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce This is a regression of https://github.com/better-auth/better-auth/issues/4080 Create an API key with 1.3.25, use it, it works. Upgrade to 1.3.26, use same API key, it does not work. ### Current vs. Expected behavior API Keys created before 1.3.26 shoudl continue to work. ### What version of Better Auth are you using? 1.3.27 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:45 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6000", "release": "25.0.0", "cpuCount": 10, "cpuModel": "Apple M1 Pro", "totalMemory": "32.00 GB", "freeMemory": "0.22 GB" }, "node": { "version": "v22.16.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": [ { "name": "svelte", "version": "^5.40.0" }, { "name": "@sveltejs/kit", "version": "2.46.5" }, { "name": "express", "version": "5.1.0" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.6" } ], "betterAuth": { "version": "1.3.26", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript ``` ### Additional context I will try to test a 1.4 beta soon and follow up.
GiteaMirror added the lockedbug labels 2026-04-17 18:37:53 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 18, 2025):

This regression with API keys breaking after upgrading to 1.3.26/1.3.27 is confirmed and tracked in issue #4080. The root cause is a change in how the API key plugin validates the x-api-key header and session retrieval logic, which started in 1.3.7 and persists in later versions, resulting in 401 Unauthorized errors for previously valid keys.

A recent fix was merged (PR #5317) that updates the authorization logic for API key handling, aligning it with the logic used for creating API keys. This should resolve flawed checks that caused the regression. If you are still seeing issues in 1.3.27, keep an eye out for the next release or try the 1.4 beta when available.

As a workaround, if you’re using API keys in a backend route (especially with frameworks like SvelteKit or Next.js), make sure to manually wrap the returned session object in a Response and ensure all error branches do the same. This helps avoid silent failures and ensures proper error handling until the fix is released.

For debugging, check the errorWithStack property in the APIError object for more details on authentication failures.

Let me know if you need help with workaround code or want to test the fix in a specific release.

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:3417687971 --> @dosubot[bot] commented on GitHub (Oct 18, 2025): <!-- Answer --> This regression with API keys breaking after upgrading to 1.3.26/1.3.27 is confirmed and tracked in [issue #4080](https://github.com/better-auth/better-auth/issues/4080). The root cause is a change in how the API key plugin validates the `x-api-key` header and session retrieval logic, which started in 1.3.7 and persists in later versions, resulting in 401 Unauthorized errors for previously valid keys. A recent fix was merged ([PR #5317](https://github.com/better-auth/better-auth/pull/5317)) that updates the authorization logic for API key handling, aligning it with the logic used for creating API keys. This should resolve flawed checks that caused the regression. If you are still seeing issues in 1.3.27, keep an eye out for the next release or try the 1.4 beta when available. As a workaround, if you’re using API keys in a backend route (especially with frameworks like SvelteKit or Next.js), make sure to manually wrap the returned session object in a `Response` and ensure all error branches do the same. This helps avoid silent failures and ensures proper error handling until the fix is released. For debugging, check the `errorWithStack` property in the `APIError` object for more details on authentication failures. Let me know if you need help with workaround code or want to test the fix in a specific release. <!-- 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/e9f57e7e-e52f-4029-a8ed-d5c68120bc88?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/e9f57e7e-e52f-4029-a8ed-d5c68120bc88?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/e9f57e7e-e52f-4029-a8ed-d5c68120bc88?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/e9f57e7e-e52f-4029-a8ed-d5c68120bc88?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/e9f57e7e-e52f-4029-a8ed-d5c68120bc88?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/e9f57e7e-e52f-4029-a8ed-d5c68120bc88?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/e9f57e7e-e52f-4029-a8ed-d5c68120bc88?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/5397)
Author
Owner

@tehnrd commented on GitHub (Oct 18, 2025):

https://github.com/better-auth/better-auth/pull/5317 does not fix the issue.

<!-- gh-comment-id:3417699144 --> @tehnrd commented on GitHub (Oct 18, 2025): https://github.com/better-auth/better-auth/pull/5317 does not fix the issue.
Author
Owner

@Bekacru commented on GitHub (Oct 18, 2025):

Assuming this was the fix: https://github.com/better-auth/better-auth/pull/4083 and hence the regression.

It was caused because that change actually introduced a security issue. It allowed an API key to act like a user’s session token, which isn’t a good idea since if an API key gets leaked, it could compromise the entire user account

<!-- gh-comment-id:3418663146 --> @Bekacru commented on GitHub (Oct 18, 2025): Assuming this was the fix: https://github.com/better-auth/better-auth/pull/4083 and hence the regression. It was caused because that change actually introduced a security issue. It allowed an API key to act like a user’s session token, which isn’t a good idea since if an API key gets leaked, it could compromise the entire user account
Author
Owner

@tehnrd commented on GitHub (Oct 18, 2025):

@ping-maxwell did you delete a comment?

This is how I am creating the key. Perhaps worth noting that the currently logged in user (a super admin) is creating the key on behalf of another user.

Not following the key leak issue as any API keys if leaked would always compromise the users account. It's the responsibility of the person that created the key to store it securely, ya?

                        const key = await auth.api.createApiKey({
                                body: {
                                        name: 'Automated Process API Key',
                                        userId: automatedUser.id,
                                        rateLimitEnabled: false
                                }
                        });
<!-- gh-comment-id:3418669968 --> @tehnrd commented on GitHub (Oct 18, 2025): @ping-maxwell did you delete a comment? This is how I am creating the key. Perhaps worth noting that the currently logged in user (a super admin) is creating the key on behalf of another user. Not following the key leak issue as any API keys if leaked would always compromise the users account. It's the responsibility of the person that created the key to store it securely, ya? ``` const key = await auth.api.createApiKey({ body: { name: 'Automated Process API Key', userId: automatedUser.id, rateLimitEnabled: false } }); ```
Author
Owner

@Bekacru commented on GitHub (Oct 18, 2025):

Yeah, but we don’t want a user API key to act like a user session since that would give it more privilege than it should have. Either way that shouldn't invalidate existing api keys. It may be a breaking change based on how you're utilizing api keys. Can you tell/show me how you’re verifying and using API keys and where exactly it's breaking so I can have clear picture?

<!-- gh-comment-id:3418673078 --> @Bekacru commented on GitHub (Oct 18, 2025): Yeah, but we don’t want a user API key to act like a user session since that would give it more privilege than it should have. Either way that shouldn't invalidate existing api keys. It may be a breaking change based on how you're utilizing api keys. Can you tell/show me how you’re verifying and using API keys and where exactly it's breaking so I can have clear picture?
Author
Owner

@tehnrd commented on GitHub (Oct 18, 2025):

And yes, #4083 fixes this the first time it stopped worked, the. It appears to have broken again with 1.3.26

<!-- gh-comment-id:3418673160 --> @tehnrd commented on GitHub (Oct 18, 2025): And yes, #4083 fixes this the first time it stopped worked, the. It appears to have broken again with 1.3.26
Author
Owner

@tehnrd commented on GitHub (Oct 18, 2025):

Will provide a more detailed example later today!

<!-- gh-comment-id:3418673717 --> @tehnrd commented on GitHub (Oct 18, 2025): Will provide a more detailed example later today!
Author
Owner

@tehnrd commented on GitHub (Oct 18, 2025):

we don’t want a user API key to act like a user session since that would give it more privilege than it should have.

Such as? I was under impression an API key would replicate all the permissions and access that use has as if they logged in with a normal session.

Is it documented what privileges they don't have when using an API vs a normal login session?

<!-- gh-comment-id:3418678455 --> @tehnrd commented on GitHub (Oct 18, 2025): > we don’t want a user API key to act like a user session since that would give it more privilege than it should have. Such as? I was under impression an API key would replicate all the permissions and access that use has as if they logged in with a normal session. Is it documented what privileges they don't have when using an API vs a normal login session?
Author
Owner

@Bekacru commented on GitHub (Oct 18, 2025):

Such as? I was under impression an API key would replicate all the permissions and access that use has as if they logged in with a normal session.

That shouldn’t be the case. We’ll make it clearer, but API keys aren’t meant to replace sessions, they have specific permissions and capabilities. It’s very easy to leak an API key, especially when it’s used in scenarios where multiple people have access to it. That should never mean a user can be fully emulated with just that key. In v1.4, we’ll add support for more granular permissions, so in certain cases you’ll be able to treat an API key like a session to make it easier to call Better Auth exposed endpoints like, creating/updating an org...

<!-- gh-comment-id:3418721489 --> @Bekacru commented on GitHub (Oct 18, 2025): > Such as? I was under impression an API key would replicate all the permissions and access that use has as if they logged in with a normal session. That shouldn’t be the case. We’ll make it clearer, but API keys aren’t meant to replace sessions, they have specific permissions and capabilities. It’s very easy to leak an API key, especially when it’s used in scenarios where multiple people have access to it. That should never mean a user can be fully emulated with just that key. In v1.4, we’ll add support for more granular permissions, so in certain cases you’ll be able to treat an API key like a session to make it easier to call Better Auth exposed endpoints like, creating/updating an org...
Author
Owner

@tehnrd commented on GitHub (Oct 19, 2025):

So it was definitely https://github.com/better-auth/better-auth/commit/a49e5aca that caused the issue, but the keys are working. 1.3.26 changes the default behavior of sessionForAPI keys from true to false. This appears to be a breaking change, so I probably would have expected it in 1.x release.

Previously, if you included the API Key in the request headers, this would work:

const session = await auth.api.getSession({
  headers: event?.request?.headers
});

...but it stopped with 1.3.26 unless you updated the API config to:

apiKey({
  enableSessionForAPIKeys: true
})

I read the docs, and see this disclaimer was added:

This is generally not recommended, as it can lead to security issues if not used carefully. A leaked api key can be used to impersonate a user.

...but I still don't understand this decision. Yes...If an API key is leaked, it can obviously impersonate a user. In the same way, if a password is leaked. An API key should be treated like a password, but as the app developer, it is not my responsibility to ensure the users secure their keys properly.

What are API Keys even suitable for if they don't create a session? Does a client have to authenticate in a different way, and then also pass in an API Key? Why, if they have already authenticated with a different method? I have never seen this before.

EDIT: After reviewing the docs further, I see that I can check permissions for an API Key, but how do I determine which user the key is associated with? I guess I don't care about the session specifically, but I would expect to be able to determine the user when an API Key is used (especially if monitoring indicates the key is compromised, I can let the owner/user know). Having anonymous requests not bound to a user seems far worse for auditing and tracking purposes for my use cases. And if I have a different permissioning outside of better-auth with something like CASL I need to know who the user is in order to fetch their role/permissions.

<!-- gh-comment-id:3419117497 --> @tehnrd commented on GitHub (Oct 19, 2025): So it was definitely https://github.com/better-auth/better-auth/commit/a49e5aca that caused the issue, but the keys _are_ working. 1.3.26 changes the default behavior of sessionForAPI keys from true to false. This appears to be a breaking change, so I probably would have expected it in 1.x release. Previously, if you included the API Key in the request headers, this would work: ``` const session = await auth.api.getSession({ headers: event?.request?.headers }); ``` ...but it stopped with 1.3.26 unless you updated the API config to: ``` apiKey({ enableSessionForAPIKeys: true }) ``` I read the docs, and see this disclaimer was added: ``` This is generally not recommended, as it can lead to security issues if not used carefully. A leaked api key can be used to impersonate a user. ``` ...but I still don't understand this decision. Yes...If an API key is leaked, it can obviously impersonate a user. In the same way, if a password is leaked. An API key should be treated like a password, but as the app developer, it is not my responsibility to ensure the users secure their keys properly. What are API Keys even suitable for if they don't create a session? Does a client have to authenticate in a different way, and then also pass in an API Key? Why, if they have already authenticated with a different method? I have never seen this before. **EDIT**: After reviewing the docs further, I see that I can check permissions for an API Key, but how do I determine which user the key is associated with? I guess I don't care about the session specifically, but I would expect to be able to determine the user when an API Key is used (especially if monitoring indicates the key is compromised, I can let the owner/user know). Having anonymous requests not bound to a user seems far worse for auditing and tracking purposes for my use cases. And if I have a different permissioning outside of better-auth with something like [CASL](https://casl.js.org/v6/en/) I need to know who the user is in order to fetch their role/permissions.
Author
Owner

@Bekacru commented on GitHub (Oct 21, 2025):

how do I determine which user the key is associated with?

The api key schema has userId. You can use that to determine which user own that api key

<!-- gh-comment-id:3429520913 --> @Bekacru commented on GitHub (Oct 21, 2025): > how do I determine which user the key is associated with? The api key schema has `userId`. You can use that to determine which user own that api key
Author
Owner

@tehnrd commented on GitHub (Oct 21, 2025):

Got it, thank you.

Verifying an API key will provide the userId. So with API Keys I need to inspect the request, see if api key header is present, verify the key, and then fetch the user.

API key verification result: {
  valid: true,
  error: null,
  key: {
    name: 'Automated Process API Key',
    start: 'MfOLnH',
    prefix: null,
    userId: '0198b455-3fd4-73d9-aa77-f9631d9e43c2',
    refillInterval: null,
    refillAmount: null,
    lastRefillAt: null,
    enabled: true,
    rateLimitEnabled: false,
    rateLimitTimeWindow: 86400000,
    rateLimitMax: 10,
    requestCount: 0,
    remaining: null,
    lastRequest: 2025-10-21T22:11:21.850Z,
    expiresAt: null,
    createdAt: 2025-09-23T00:48:59.703Z,
    updatedAt: 2025-09-23T00:48:59.703Z,
    permissions: null,
    metadata: null,
    id: '0199740b-9337-700b-bd0e-60f9b284af80'
  }
}
<!-- gh-comment-id:3429771330 --> @tehnrd commented on GitHub (Oct 21, 2025): Got it, thank you. Verifying an API key will provide the userId. So with API Keys I need to inspect the request, see if api key header is present, verify the key, and then fetch the user. ``` API key verification result: { valid: true, error: null, key: { name: 'Automated Process API Key', start: 'MfOLnH', prefix: null, userId: '0198b455-3fd4-73d9-aa77-f9631d9e43c2', refillInterval: null, refillAmount: null, lastRefillAt: null, enabled: true, rateLimitEnabled: false, rateLimitTimeWindow: 86400000, rateLimitMax: 10, requestCount: 0, remaining: null, lastRequest: 2025-10-21T22:11:21.850Z, expiresAt: null, createdAt: 2025-09-23T00:48:59.703Z, updatedAt: 2025-09-23T00:48:59.703Z, permissions: null, metadata: null, id: '0199740b-9337-700b-bd0e-60f9b284af80' } } ```
Author
Owner

@florianmartens commented on GitHub (Oct 22, 2025):

This caused a prod incident for me. I really don't think a change like this should be in a patch version bump (this justifies a major version bump). It's clearly a breaking change.

Changing to this, fixed it for me (for now):

export const auth = betterAuth({
  plugins: [
    apiKey({
      enableSessionForAPIKeys: true,
    }),
  ],
});

I agree with the general concept here but think this will reduce the ease-of-use of the library going forward:

That shouldn’t be the case. We’ll make it clearer, but API keys aren’t meant to replace sessions, they have specific permissions and capabilities.

The idea behind well-designed auth should be that we have a predictable post-validation payload that tells us who the user is. Better-auth has chosen the session object to be the common denominator between auth methods that can be passed to a handler irrespective of the authentication method.

When talking about sessions replacing giving extra-permissions, were talking about authorization and that should be it's own thing in user-land. Why not just have a type on the session (api key, vs. cookie) or attach the api key object to the request so users can take care of authorization themselves?

Anyway, really appreciate your work on this library @Bekacru ... but this far to big of a change for a patch version upgrade and can easily break many apps in prod that install without a frozen lockfile, etc.

<!-- gh-comment-id:3430903085 --> @florianmartens commented on GitHub (Oct 22, 2025): This caused a prod incident for me. I really don't think a change like this should be in a patch version bump (this justifies a major version bump). It's clearly a breaking change. Changing to this, fixed it for me (for now): ```ts export const auth = betterAuth({ plugins: [ apiKey({ enableSessionForAPIKeys: true, }), ], }); ``` I agree with the general concept here but think this will reduce the ease-of-use of the library going forward: > That shouldn’t be the case. We’ll make it clearer, but API keys aren’t meant to replace sessions, they have specific permissions and capabilities. The idea behind well-designed auth should be that we have a predictable post-validation payload that tells us who the user is. Better-auth has chosen the `session` object to be the common denominator between auth methods that can be passed to a handler irrespective of the authentication method. When talking about sessions replacing giving extra-permissions, were talking about authorization and that should be it's own thing in user-land. Why not just have a type on the session (api key, vs. cookie) or attach the api key object to the request so users can take care of authorization themselves? Anyway, really appreciate your work on this library @Bekacru ... but this far to big of a change for a patch version upgrade and can easily break many apps in prod that install without a frozen lockfile, etc.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27555