[GH-ISSUE #6258] verifyApiKey returns "Invalid API key" error after creating API key with createApiKey #10462

Closed
opened 2026-04-13 06:37:41 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @Sw0xy on GitHub (Nov 24, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6258

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

I'm experiencing an issue where verifyApiKey consistently returns an "Invalid API key" error, even though the API key was successfully created using createApiKey.


Environment:

  • Better Auth version: 1.3.34
  • Database: PostgreSQL (using Prisma)
  • Framework: Next.js (App Router)
  • Node version: 22.16
  • Turborepo

When I create an API key using auth.api.createApiKey() in my tRPC route and then try to verify it using auth.api.verifyApiKey() in a Next.js API route, the verification fails with the following error:

{ 
  "message": "Invalid API key.", 
  "code": "INVALID_API_KEY" 
}

Prisma Schema:

model Apikey {
    id                     String                @id @default(cuid())
    name                   String?
    start                  String?
    prefix                 String?
    key                    String
    userId                 String
    user                   User                  @relation(fields: [userId], references: [id], onDelete: Cascade)
    refillInterval         Int?
    refillAmount           Int?
    lastRefillAt           DateTime?
    enabled                Boolean?              @default(true)
    rateLimitEnabled       Boolean?              @default(true)
    rateLimitTimeWindow    Int?
    rateLimitMax           Int?
    requestCount           Int?
    remaining              Int?
    lastRequest            DateTime?
    expiresAt              DateTime?
    createdAt              DateTime              @default(now())
    updatedAt              DateTime              @updatedAt
    permissions            String?
    metadata               String?
    installedIntegration   InstalledIntegration? @relation(fields: [installedIntegrationId], references: [id])
    installedIntegrationId String?

    @@map("apikey")
}

Code to Create API Key (tRPC Route):

const apiKeyResult = await auth.api.createApiKey({
  body: {
    userId: ctx.user.id,
    remaining: 100,
    metadata: {
      installationId: newIntegration.id,
      integrationId: INTEGRATION_ID,
      shop: shop,
    },
    refillAmount: 100,
    refillInterval: 1000, // 1 second
    rateLimitTimeWindow: 1000,
    rateLimitMax: 200,
    rateLimitEnabled: true,
  },
});

Code to Verify API Key (Next.js API Route):

export const POST = async (req: NextApiRequest) => {
  try {
    const parseResult = schema.safeParse(await parseRequestBody(req));

    if (!parseResult.success) {
      return NextResponse.json(
        { success: false, error: "Invalid request parameters" },
        { status: 400 },
      );
    }

    const { apiKey } = parseResult.data;

    const { error, key, valid } = await auth.api.verifyApiKey({
      body: {
        key: apiKey,
      },
    });

    console.log("API KEY ERROR", error, apiKey, valid);

    return NextResponse.json({ success: true, key }, { status: 200 });
  } catch {
    return NextResponse.json(
      { success: false, error: "Invalid request parameters" },
      { status: 400 },
    );
  }
};

Error Output:

API KEY ERROR { message: 'Invalid API key.', code: 'INVALID_API_KEY' } <api-key-value> undefined

Current vs. Expected behavior

Expected Behavior:

verifyApiKey should successfully validate the API key and return valid: true with the key details.

Actual Behavior:

verifyApiKey returns an error object with { message: 'Invalid API key.', code: 'INVALID_API_KEY' }

What version of Better Auth are you using?

1.3.34

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:56 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8132",
    "release": "25.1.0",
    "cpuCount": 10,
    "cpuModel": "Apple M4",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.28 GB"
  },
  "node": {
    "version": "v22.16.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.4.2"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null,
    "error": "ENOENT: no such file or directory, open 'package.json'"
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"

apiKey({
      defaultKeyLength: 43,
      rateLimit: {
        maxRequests: 50,
        timeWindow: 3 * 60 * 1000, // 3 minute
        enabled: true,
      },
      enableMetadata: true,
      permissions: {
        defaultPermissions: {
          analytics: ["write"],
          organizations: ["read", "write"],
        },
      },
    }),

Additional context

Additional Context:

  • The API key is being created successfully (no errors during creation)
  • The apiKey value being passed to verifyApiKey appears to be correct
  • Database connection is working fine for other operations

Any guidance would be greatly appreciated! 🙏

Originally created by @Sw0xy on GitHub (Nov 24, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6258 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce I'm experiencing an issue where `verifyApiKey` consistently returns an "Invalid API key" error, even though the API key was successfully created using `createApiKey`. --- ### **Environment:** - Better Auth version: 1.3.34 - Database: PostgreSQL (using Prisma) - Framework: Next.js (App Router) - Node version: 22.16 - Turborepo When I create an API key using `auth.api.createApiKey()` in my tRPC route and then try to verify it using `auth.api.verifyApiKey()` in a Next.js API route, the verification fails with the following error: ```json { "message": "Invalid API key.", "code": "INVALID_API_KEY" } ``` ### **Prisma Schema:** ```prisma model Apikey { id String @id @default(cuid()) name String? start String? prefix String? key String userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) refillInterval Int? refillAmount Int? lastRefillAt DateTime? enabled Boolean? @default(true) rateLimitEnabled Boolean? @default(true) rateLimitTimeWindow Int? rateLimitMax Int? requestCount Int? remaining Int? lastRequest DateTime? expiresAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt permissions String? metadata String? installedIntegration InstalledIntegration? @relation(fields: [installedIntegrationId], references: [id]) installedIntegrationId String? @@map("apikey") } ``` --- ### **Code to Create API Key (tRPC Route):** ```typescript const apiKeyResult = await auth.api.createApiKey({ body: { userId: ctx.user.id, remaining: 100, metadata: { installationId: newIntegration.id, integrationId: INTEGRATION_ID, shop: shop, }, refillAmount: 100, refillInterval: 1000, // 1 second rateLimitTimeWindow: 1000, rateLimitMax: 200, rateLimitEnabled: true, }, }); ``` --- ### **Code to Verify API Key (Next.js API Route):** ```typescript export const POST = async (req: NextApiRequest) => { try { const parseResult = schema.safeParse(await parseRequestBody(req)); if (!parseResult.success) { return NextResponse.json( { success: false, error: "Invalid request parameters" }, { status: 400 }, ); } const { apiKey } = parseResult.data; const { error, key, valid } = await auth.api.verifyApiKey({ body: { key: apiKey, }, }); console.log("API KEY ERROR", error, apiKey, valid); return NextResponse.json({ success: true, key }, { status: 200 }); } catch { return NextResponse.json( { success: false, error: "Invalid request parameters" }, { status: 400 }, ); } }; ``` --- ### **Error Output:** ```javascript API KEY ERROR { message: 'Invalid API key.', code: 'INVALID_API_KEY' } <api-key-value> undefined ``` ### Current vs. Expected behavior ### **Expected Behavior:** `verifyApiKey` should successfully validate the API key and return `valid: true` with the key details. ### **Actual Behavior:** `verifyApiKey` returns an error object with `{ message: 'Invalid API key.', code: 'INVALID_API_KEY' }` ### What version of Better Auth are you using? 1.3.34 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:56 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8132", "release": "25.1.0", "cpuCount": 10, "cpuModel": "Apple M4", "totalMemory": "16.00 GB", "freeMemory": "0.28 GB" }, "node": { "version": "v22.16.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.4.2" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null, "error": "ENOENT: no such file or directory, open 'package.json'" } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" apiKey({ defaultKeyLength: 43, rateLimit: { maxRequests: 50, timeWindow: 3 * 60 * 1000, // 3 minute enabled: true, }, enableMetadata: true, permissions: { defaultPermissions: { analytics: ["write"], organizations: ["read", "write"], }, }, }), ``` ### Additional context ### **Additional Context:** - The API key is being created successfully (no errors during creation) - The `apiKey` value being passed to `verifyApiKey` appears to be correct - Database connection is working fine for other operations Any guidance would be greatly appreciated! 🙏
GiteaMirror added the lockedbug labels 2026-04-13 06:37:41 -05:00
Author
Owner

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

It looks like you’re encountering the same type of issue that some users reported where API key verification fails by returning “INVALID_API_KEY” even though the key was successfully created. One thing to check is whether the API key string stored in the database exactly matches what is being passed to verifyApiKey. In some cases, extra whitespace, unexpected characters, or even a mismatched prefix (if you’re using one) can cause the verification to fail [1].

I’d recommend the following troubleshooting steps:
• Double-check that the API key value returned from createApiKey is identical (including casing and any prefix) to what’s sent in verifyApiKey.
• Verify that your database field mapping (especially for the key property) correctly preserves the key string without any unexpected formatting.
• Ensure that any conversion (for example, when storing metadata) isn’t altering the key value.
• Add some detailed logging around the created API key and the value being verified to help pinpoint any discrepancies.

Have you confirmed that the key string isn’t being modified anywhere between creation and verification? Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3569454858 --> @better-auth-agent[bot] commented on GitHub (Nov 24, 2025): It looks like you’re encountering the same type of issue that some users reported where API key verification fails by returning “INVALID_API_KEY” even though the key was successfully created. One thing to check is whether the API key string stored in the database exactly matches what is being passed to verifyApiKey. In some cases, extra whitespace, unexpected characters, or even a mismatched prefix (if you’re using one) can cause the verification to fail [[1]](https://github.com/better-auth/better-auth/issues/4125). I’d recommend the following troubleshooting steps: • Double-check that the API key value returned from createApiKey is identical (including casing and any prefix) to what’s sent in verifyApiKey. • Verify that your database field mapping (especially for the key property) correctly preserves the key string without any unexpected formatting. • Ensure that any conversion (for example, when storing metadata) isn’t altering the key value. • Add some detailed logging around the created API key and the value being verified to help pinpoint any discrepancies. Have you confirmed that the key string isn’t being modified anywhere between creation and verification? Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 24, 2025):

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

The most common cause for this issue is that verifyApiKey expects the exact raw API key string returned by createApiKey—not the hashed value stored in your database, and not a value retrieved from the database or altered in any way. The raw key is only available at creation time; if you lose it or try to verify using a different value, you'll always get "Invalid API key" (source, source, source).

Double-check that the value you pass to verifyApiKey is exactly the one returned by createApiKey at creation time. If you are storing the key somewhere, make sure you are not accidentally saving the hashed version or a truncated value.

Also, in Better Auth v1.3.26 and later, API keys do not create sessions by default. If you expect session-like behavior, set enableSessionForAPIKeys: true in your apiKey plugin config (source).

If you’re still seeing issues, add debug logs to print the key at creation and at verification to ensure they match exactly, and check for any differences in environment or config between your tRPC and Next.js API routes.

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:3569466743 --> @dosubot[bot] commented on GitHub (Nov 24, 2025): <!-- Greeting --> Hi @Sw0xy! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The most common cause for this issue is that `verifyApiKey` expects the exact raw API key string returned by `createApiKey`—not the hashed value stored in your database, and not a value retrieved from the database or altered in any way. The raw key is only available at creation time; if you lose it or try to verify using a different value, you'll always get "Invalid API key" ([source](https://github.com/better-auth/better-auth/issues/2530), [source](https://github.com/better-auth/better-auth/blob/fc662c52dcadc9874d36bd354d53d23f8a24e098/packages/better-auth/src/plugins/api-key/routes/create-api-key.ts#L356-L480), [source](https://github.com/better-auth/better-auth/blob/fc662c52dcadc9874d36bd354d53d23f8a24e098/packages/better-auth/src/plugins/api-key/routes/verify-api-key.ts#L28-L329)). Double-check that the value you pass to `verifyApiKey` is exactly the one returned by `createApiKey` at creation time. If you are storing the key somewhere, make sure you are not accidentally saving the hashed version or a truncated value. Also, in Better Auth v1.3.26 and later, API keys do not create sessions by default. If you expect session-like behavior, set `enableSessionForAPIKeys: true` in your `apiKey` plugin config ([source](https://github.com/better-auth/better-auth/issues/5397)). If you’re still seeing issues, add debug logs to print the key at creation and at verification to ensure they match exactly, and check for any differences in environment or config between your tRPC and Next.js API routes. <!-- 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/d3c26586-7438-430e-a835-fa636297a27c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/d3c26586-7438-430e-a835-fa636297a27c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/d3c26586-7438-430e-a835-fa636297a27c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/d3c26586-7438-430e-a835-fa636297a27c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/d3c26586-7438-430e-a835-fa636297a27c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/d3c26586-7438-430e-a835-fa636297a27c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/d3c26586-7438-430e-a835-fa636297a27c?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=The%20most%20common%20cause%20for%20this%20issue%20is%20that%20%60verifyApiKey%60%20expects%20the%20exact%20raw%20API%20key%20string%20returned%20by%20%60createApiKey%60%E2%80%94not%20the%20hashed%20value%20stored%20in%20your%20database%2C%20and%20not%20a%20value%20retrieved%20from%20the%20database%20or%20altered%20in%20any%20way.%20The%20raw%20key%20is%20only%20available%20at%20creation%20time%3B%20if%20you%20lose%20it%20or%20try%20to%20verify%20using%20a%20different%20value%2C%20you%27ll%20always%20get%20%22Invalid%20API%20key%22%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/2530%29%2C%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/fc662c52dcadc9874d36bd354d53d23f8a24e098/packages/better-auth/src/plugins/api-key/routes/create-api-key.ts%23L356-L480%29%2C%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/fc662c52dcadc9874d36bd354d53d23f8a24e098/packages/better-auth/src/plugins/api-key/routes/verify-api-key.ts%23L28-L329%29%29.%0A%0ADouble-check%20that%20the%20value%20you%20pass%20to%20%60verifyApiKey%60%20is%20exactly%20the%20one%20returned%20by%20%60createApiKey%60%20at%20creation%20time.%20If%20you%20are%20storing%20the%20key%20somewhere%2C%20make%20sure%20you%20are%20not%20accidentally%20saving%20the%20hashed%20version%20or%20a%20truncated%20value.%0A%0AAlso%2C%20in%20Better%20Auth%20v1.3.26%20and%20later%2C%20API%20keys%20do%20not%20create%20sessions%20by%20default.%20If%20you%20expect%20session-like%20behavior%2C%20set%20%60enableSessionForAPIKeys%3A%20true%60%20in%20your%20%60apiKey%60%20plugin%20config%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/5397%29%29.%0A%0AIf%20you%E2%80%99re%20still%20seeing%20issues%2C%20add%20debug%20logs%20to%20print%20the%20key%20at%20creation%20and%20at%20verification%20to%20ensure%20they%20match%20exactly%2C%20and%20check%20for%20any%20differences%20in%20environment%20or%20config%20between%20your%20tRPC%20and%20Next.js%20API%20routes.)&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/6258)
Author
Owner

@francip commented on GitHub (Dec 8, 2025):

I am hitting the same issue with better auth 1.4.5.
I have disabled the key hashing and verified in Mongo that the key value in the db is exactly the same I got through authClient.apiKey.create() and that I am passing to auth.api.verifyApiKey()
I am not using next.js, this is happening locally between a vite hosted react client and node.js server.

<!-- gh-comment-id:3624639054 --> @francip commented on GitHub (Dec 8, 2025): I am hitting the same issue with better auth 1.4.5. I have disabled the key hashing and verified in Mongo that the key value in the db is exactly the same I got through authClient.apiKey.create() and that I am passing to auth.api.verifyApiKey() I am not using next.js, this is happening locally between a vite hosted react client and node.js server.
Author
Owner

@DariusCorvus commented on GitHub (Dec 8, 2025):

same problem using surrealdb-better-auth

<!-- gh-comment-id:3627317167 --> @DariusCorvus commented on GitHub (Dec 8, 2025): same problem using surrealdb-better-auth
Author
Owner

@evnydd0sf commented on GitHub (Dec 12, 2025):

I also encountered this issue and saw that it has already been fixed in #6259, but I haven't seen it merged yet, so I can only wait...

<!-- gh-comment-id:3644801896 --> @evnydd0sf commented on GitHub (Dec 12, 2025): I also encountered this issue and saw that it has already been fixed in #6259, but I haven't seen it merged yet, so I can only wait...
Author
Owner

@francip commented on GitHub (Dec 12, 2025):

Looking through the code and the db state, and playing around with different options, here's what I found out so far:

  • createApiKey creates the key properly and stores it in the DB, respecting the hashing config
  • verifying with a custom key validator - the validator receives the proper key
  • the apikey collection (in Mongo at least) is created without index on key
  • verifyApiKey consistently fails to find the key in the db

As a workaround I have implemented my own validation that literally does the same thing verifyApiKey() does - finds the key in the db and checks enabled and expiresAt.

<!-- gh-comment-id:3647904323 --> @francip commented on GitHub (Dec 12, 2025): Looking through the code and the db state, and playing around with different options, here's what I found out so far: - `createApiKey` creates the key properly and stores it in the DB, respecting the hashing config - verifying with a custom key validator - the validator receives the proper key - the `apikey` collection (in Mongo at least) is created without index on `key` - `verifyApiKey` consistently fails to find the key in the db As a workaround I have implemented my own validation that literally does the same thing verifyApiKey() does - finds the key in the db and checks `enabled` and `expiresAt`.
Author
Owner

@0xrabbitz commented on GitHub (Dec 18, 2025):

same issue with better-auth 1.4.7.
apiKey is returned when creating the request, but when I use this key in verifyApiKey, it throws an error:
{ message: 'Invalid API key.', code: 'INVALID_API_KEY' }

<!-- gh-comment-id:3669307284 --> @0xrabbitz commented on GitHub (Dec 18, 2025): same issue with better-auth 1.4.7. apiKey is returned when creating the request, but when I use this key in verifyApiKey, it throws an error: { message: 'Invalid API key.', code: 'INVALID_API_KEY' }
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10462