Skip to content

[bug] BetterAuthInstance.getSession headers type is wider than Better Auth's, breaks TS assignability #323

@TaylorFacen

Description

@TaylorFacen

Summary

createAuthMiddleware (and createAuthIdentifier) declare BetterAuthInstance.getSession with a wider headers parameter than Better Auth's actual auth.api.getSession. Because function parameters are contravariant, passing a real auth instance fails type-checking under strict / strictFunctionTypes.

Type declared by evlog

// dist/better-auth/index.d.mts
interface BetterAuthInstance {
  api: {
    getSession: (opts: {
      headers: Headers | Record<string, string | string[] | undefined>;
    }) => Promise<{ user: Record<string, unknown>; session: Record<string, unknown> } | null>;
  };
}

Type provided by Better Auth

getSession: (context: { headers: Headers; ... }) => Promise<...>

Better Auth only accepts Headers, not the wider union — so its getSession is not assignable to evlog's slot.

Reproduction

import { betterAuth } from 'better-auth';
import { createAuthMiddleware } from 'evlog/better-auth';

const auth = betterAuth({ /* any config */ });
export const identify = createAuthMiddleware(auth);
//                                            ^^^^
// TS2345: Argument of type 'Auth<...>' is not assignable to parameter of type 'BetterAuthInstance'.
//   The types of 'api.getSession' are incompatible between these types.
//     Type '(<R, H>(context: { headers: Headers; ... }) => ...)' is not assignable to
//          '(opts: { headers: Headers | Record<...>; }) => ...'.
//       Types of property 'headers' are incompatible.
//         Type 'Headers | Record<...>' is not assignable to 'Headers'.

Versions

  • evlog: 2.16.0
  • better-auth: 1.6.9 (also reproduces on 1.5.6)
  • typescript: 5.x with strict: true

Suggested fix

Narrow the interface to just Headers, since that is what every Better Auth release actually accepts:

interface BetterAuthInstance {
  api: {
    getSession: (opts: { headers: Headers }) => Promise<{ user: Record<string, unknown>; session: Record<string, unknown> } | null>;
  };
}

If support for header records is desired internally, evlog can normalize them to Headers before calling auth.api.getSession.

Workaround

Wrap the auth instance with a small adapter that converts the union to Headers before calling through:

function toHeaders(input: Headers | Record<string, string | string[] | undefined>): Headers {
  if (input instanceof Headers) return input;
  const h = new Headers();
  for (const [k, v] of Object.entries(input)) {
    if (v === undefined) continue;
    Array.isArray(v) ? v.forEach(x => h.append(k, x)) : h.set(k, v);
  }
  return h;
}

export const identify = createAuthMiddleware({
  api: { getSession: ({ headers }) => auth.api.getSession({ headers: toHeaders(headers) }) },
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions