[GH-ISSUE #3722] SvelteKit apiKey integration guidance #9698

Closed
opened 2026-04-13 05:21:08 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @rebasecase on GitHub (Jul 31, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3722

Describe the solution you'd like

Some guidance on handling failure responses from API-only routes, I would like to know how to catch the error response of the middleware and return custom responses.

Describe alternatives you've considered

I haven't been able to work it out yet

Originally created by @rebasecase on GitHub (Jul 31, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3722 ### Describe the solution you'd like Some guidance on handling failure responses from API-only routes, I would like to know how to catch the error response of the middleware and return custom responses. ### Describe alternatives you've considered I haven't been able to work it out yet
GiteaMirror added the locked label 2026-04-13 05:21:08 -05:00
Author
Owner

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

To handle failure responses from API-only routes in SvelteKit with better-auth's apiKey integration, catch errors thrown by the middleware (typically APIError instances) in your route handlers and return custom responses as needed. The middleware throws errors for invalid or missing API keys, unauthorized users, etc., which you can intercept and format for your API consumers.

Here's a practical example for a SvelteKit endpoint (e.g., src/routes/api/protected/+server.ts):

import { auth } from '$lib/auth'; // your better-auth instance
import { error, json } from '@sveltejs/kit';

export const GET = async ({ request }) => {
  try {
    // This will throw if the API key is invalid or missing
    const session = await auth.api.getSession({ headers: request.headers });

    // Your protected logic here
    return json({ user: session.user, session: session.session });
  } catch (err) {
    // Handle better-auth APIError or generic errors
    if (err?.name === 'APIError') {
      // Customize the response based on error code/message
      return json(
        { error: err.message, code: err.code },
        { status: err.code === 'FORBIDDEN' ? 403 : 401 }
      );
    }
    // For other errors, return a generic 500
    throw error(500, 'Internal Server Error');
  }
};

You can adapt this pattern for other HTTP methods. The key is to wrap your session/auth logic in a try/catch and inspect the error object for custom handling. The same approach works for endpoints using the apiKey plugin, since errors are thrown as APIError instances with codes like FORBIDDEN, UNAUTHORIZED, etc. source

For global handling (e.g., all protected routes), you can also use the handle hook in hooks.server.ts to redirect or respond based on session state, as shown in the official example: if session retrieval fails, redirect to /sign-in or return a custom error response source.

This pattern gives you full control over error responses for API consumers while leveraging better-auth's built-in error signaling.

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:3141018769 --> @dosubot[bot] commented on GitHub (Jul 31, 2025): <!-- Answer --> To handle failure responses from API-only routes in SvelteKit with better-auth's `apiKey` integration, catch errors thrown by the middleware (typically `APIError` instances) in your route handlers and return custom responses as needed. The middleware throws errors for invalid or missing API keys, unauthorized users, etc., which you can intercept and format for your API consumers. Here's a practical example for a SvelteKit endpoint (e.g., `src/routes/api/protected/+server.ts`): ```typescript import { auth } from '$lib/auth'; // your better-auth instance import { error, json } from '@sveltejs/kit'; export const GET = async ({ request }) => { try { // This will throw if the API key is invalid or missing const session = await auth.api.getSession({ headers: request.headers }); // Your protected logic here return json({ user: session.user, session: session.session }); } catch (err) { // Handle better-auth APIError or generic errors if (err?.name === 'APIError') { // Customize the response based on error code/message return json( { error: err.message, code: err.code }, { status: err.code === 'FORBIDDEN' ? 403 : 401 } ); } // For other errors, return a generic 500 throw error(500, 'Internal Server Error'); } }; ``` You can adapt this pattern for other HTTP methods. The key is to wrap your session/auth logic in a try/catch and inspect the error object for custom handling. The same approach works for endpoints using the `apiKey` plugin, since errors are thrown as `APIError` instances with codes like `FORBIDDEN`, `UNAUTHORIZED`, etc. [source](https://github.com/better-auth/better-auth/blob/0656f3ef95dcaee777e9b1510a42f7aa5593e732/packages/better-auth/src/plugins/api-key/index.ts) For global handling (e.g., all protected routes), you can also use the `handle` hook in `hooks.server.ts` to redirect or respond based on session state, as shown in the official example: if session retrieval fails, redirect to `/sign-in` or return a custom error response [source](https://github.com/better-auth/better-auth/pull/1742). This pattern gives you full control over error responses for API consumers while leveraging better-auth's built-in error signaling. <!-- 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/9dc40eda-074e-4538-82f8-ad33b6265982?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9dc40eda-074e-4538-82f8-ad33b6265982?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9dc40eda-074e-4538-82f8-ad33b6265982?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9dc40eda-074e-4538-82f8-ad33b6265982?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9dc40eda-074e-4538-82f8-ad33b6265982?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9dc40eda-074e-4538-82f8-ad33b6265982?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9dc40eda-074e-4538-82f8-ad33b6265982?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/3722)
Author
Owner

@rebasecase commented on GitHub (Jul 31, 2025):

@dosu bad bot. It should be on the middleware layer surely since the auth happens before the page is handled

<!-- gh-comment-id:3141192324 --> @rebasecase commented on GitHub (Jul 31, 2025): @dosu bad bot. It should be on the middleware layer surely since the auth happens before the page is handled
Author
Owner

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

You're right—the best place to globally handle and customize error responses for better-auth (including apiKey) in SvelteKit is the middleware layer, specifically in your hooks.server.ts handle function. Errors thrown by better-auth (like APIError from the apiKey middleware) will bubble up to this layer, so you can intercept them and shape the response however you want.

Here's how you can do it:

import { auth } from '$lib/auth';
import { svelteKitHandler } from 'better-auth/svelte-kit';
import { building } from '$app/environment';

export async function handle({ event, resolve }) {
  try {
    return await svelteKitHandler({ event, resolve, auth, building });
  } catch (err) {
    // Customize APIError responses globally
    if (err?.name === 'APIError') {
      return new Response(
        JSON.stringify({ error: err.message, code: err.code }),
        {
          status: err.code === 'FORBIDDEN' ? 403 : 401,
          headers: { 'Content-Type': 'application/json' }
        }
      );
    }
    // Fallback for other errors
    return new Response('Internal Server Error', { status: 500 });
  }
}

This pattern gives you full control over all error responses from better-auth at the middleware layer, before they reach any route handler. You can extend the logic to handle other error types or add logging as needed. This is the recommended approach for global error shaping with better-auth in SvelteKit [docs][apiKey impl].

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:3141202468 --> @dosubot[bot] commented on GitHub (Jul 31, 2025): <!-- Answer --> You're right—the best place to globally handle and customize error responses for better-auth (including apiKey) in SvelteKit is the middleware layer, specifically in your `hooks.server.ts` handle function. Errors thrown by better-auth (like `APIError` from the apiKey middleware) will bubble up to this layer, so you can intercept them and shape the response however you want. Here's how you can do it: ```ts import { auth } from '$lib/auth'; import { svelteKitHandler } from 'better-auth/svelte-kit'; import { building } from '$app/environment'; export async function handle({ event, resolve }) { try { return await svelteKitHandler({ event, resolve, auth, building }); } catch (err) { // Customize APIError responses globally if (err?.name === 'APIError') { return new Response( JSON.stringify({ error: err.message, code: err.code }), { status: err.code === 'FORBIDDEN' ? 403 : 401, headers: { 'Content-Type': 'application/json' } } ); } // Fallback for other errors return new Response('Internal Server Error', { status: 500 }); } } ``` This pattern gives you full control over all error responses from better-auth at the middleware layer, before they reach any route handler. You can extend the logic to handle other error types or add logging as needed. This is the recommended approach for global error shaping with better-auth in SvelteKit [[docs](https://github.com/better-auth/better-auth/blob/0656f3ef95dcaee777e9b1510a42f7aa5593e732/docs/content/docs/integrations/svelte-kit.mdx)][[apiKey impl](https://github.com/better-auth/better-auth/blob/0656f3ef95dcaee777e9b1510a42f7aa5593e732/packages/better-auth/src/plugins/api-key/index.ts)]. <!-- 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/be31a99b-8991-4d03-aa9e-2624fd528338?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/be31a99b-8991-4d03-aa9e-2624fd528338?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/be31a99b-8991-4d03-aa9e-2624fd528338?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/be31a99b-8991-4d03-aa9e-2624fd528338?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/be31a99b-8991-4d03-aa9e-2624fd528338?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/be31a99b-8991-4d03-aa9e-2624fd528338?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/be31a99b-8991-4d03-aa9e-2624fd528338?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/3722)
Author
Owner

@rebasecase commented on GitHub (Jul 31, 2025):

I ended up with this, not sure if that's acceptable or not - but it works and returns the error in the uniform format svelte kit expects.

export const authenticationHandle: Handle = async ({ event, resolve }) => {
  try {
    return await betterAuthMiddleware({
      auth,
      event,
      resolve,
      building
    });
  } catch (err) {
    if (err instanceof APIError) {
      error(err.statusCode, {
        message: err.body?.message || 'Forbidden'
      });
    }
    throw err;
  }
};

Also regarding return json response, it depends on the accepts header, so that's fine.

Comments welcome

<!-- gh-comment-id:3141546846 --> @rebasecase commented on GitHub (Jul 31, 2025): I ended up with this, not sure if that's acceptable or not - but it works and returns the error in the uniform format svelte kit expects. ```ts export const authenticationHandle: Handle = async ({ event, resolve }) => { try { return await betterAuthMiddleware({ auth, event, resolve, building }); } catch (err) { if (err instanceof APIError) { error(err.statusCode, { message: err.body?.message || 'Forbidden' }); } throw err; } }; ``` Also regarding return json response, it depends on the accepts header, so that's fine. Comments welcome
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9698