Better Auth
Read the Better Auth session in a middleware and type ctx.user with its user.
Better Auth reads the session from the request's headers. Put the headers in the ctx, then a middleware turns the session into ctx.user:
const const t: TrpcRoot<{
headers: Headers;
}, {}, RootDef<{
headers: Headers;
}, {}>>
t = initTrpc<{
ctx: {
headers: Headers;
};
}>(opts?: RootOptions<{}>): TrpcRoot<{
headers: Headers;
}, {}, RootDef<{
headers: Headers;
}, {}>>
initTrpc<{ ctx: {
headers: Headers;
}
ctx: { headers: Headersheaders: Headers } }>();
const const authed: ProcedureBuilder<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>>
authed = const t: TrpcRoot<{
headers: Headers;
}, {}, RootDef<{
headers: Headers;
}, {}>>
t.TrpcRoot<{ headers: Headers; }, {}, RootDef<{ headers: Headers; }, {}>>.procedure: ProcedureBuilder<RootDef<{
headers: Headers;
}, {}>>
procedure.ProcedureBuilder<RootDef<{ headers: Headers; }, {}>>.use<{
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
}, TrpcError<"UNAUTHORIZED", undefined>, never, Unset, {}>(fn: MiddlewareFunction<{
headers: Headers;
}, undefined, {}, {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
}, TrpcError<...>, never, Unset>): ProcedureBuilder<...> (+1 overload)
use(async (opts: MiddlewareOpts<{
headers: Headers;
}, undefined, {}>
opts) => {
const const session: {
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
} | null
session = await const auth: Auth<BetterAuthOptions>auth.api: InferAPI<Prettify<{
readonly ok: StrictEndpoint<"/ok", {
method: "GET";
metadata: {
openapi: {
description: string;
responses: {
"200": {
description: string;
content: {
"application/json": {
schema: {
type: "object";
properties: {
ok: {
type: string;
description: string;
};
};
required: string[];
};
};
};
};
};
};
scope: "server";
};
}, {
ok: boolean;
}>;
readonly error: StrictEndpoint<"/error", {
method: "GET";
metadata: {
openapi: {
description: string;
responses: {
"200": {
description: string;
content: {
"text/html": {
schema: {
type: "string";
description: string;
};
};
};
};
};
};
scope: "server";
};
}, Response>;
... 28 more ...;
readonly accountInfo: StrictEndpoint<...>;
}>>
api.getSession: <boolean, false>(context: {
headers: Headers;
query?: {
disableCookieCache?: boolean;
disableRefresh?: boolean;
} | undefined;
asResponse?: boolean | undefined;
returnHeaders?: false | undefined;
}) => Promise<{
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
} | null> (+8 overloads)
getSession({ headers: Headersheaders: opts: MiddlewareOpts<{
headers: Headers;
}, undefined, {}>
opts.MiddlewareOpts<{ headers: Headers; }, undefined, {}>.ctx: {
headers: Headers;
}
ctx.headers: Headersheaders });
return const session: {
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
} | null
session
? opts: MiddlewareOpts<{
headers: Headers;
}, undefined, {}>
opts.MiddlewareOpts<{ headers: Headers; }, undefined, {}>.next: NextFn
<{
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
}>(opts: {
ctx: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
}) => Promise<...> (+2 overloads)
next({ ctx: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
}
ctx: { user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
}
user: const session: {
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
}
session.user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
}
user, session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
}
session: const session: {
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
}
session.session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
}
session } })
: error<"UNAUTHORIZED", undefined>(opts: Omit<TrpcErrorOptions<"UNAUTHORIZED", undefined>, "defined">): TrpcError<"UNAUTHORIZED", undefined>error({ code: "UNAUTHORIZED"code: 'UNAUTHORIZED' });
});
const const router: {
me: BuildProcedure<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, "query", {
output: string;
errors: never;
services: never;
unmapped: never;
}>;
}
router = {
me: BuildProcedure<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, "query", {
output: string;
errors: never;
services: never;
unmapped: never;
}>
me: const authed: ProcedureBuilder<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>>
authed.ProcedureBuilder<With<RootDef<{ headers: Headers; }, {}>, { ctx: { headers: Headers; }; ctxAdded: { user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined | undefined; }; session: { ...; }; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>>.query<string>(resolver: (opts: ResolverOpts<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>>) => string): BuildProcedure<...>
query((opts: ResolverOpts<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>>
opts) => opts: ResolverOpts<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>>
opts.ResolverOpts<With<RootDef<{ headers: Headers; }, {}>, { ctx: { headers: Headers; }; ctxAdded: { user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined | undefined; }; session: { ...; }; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>>.ctx: {
headers: Headers;
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
}
ctx.user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
}
user.name: stringname), // ctx.user is Better Auth's user type
};
createFetchHandler<{
me: BuildProcedure<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, "query", {
output: string;
errors: never;
services: never;
unmapped: never;
}>;
}, undefined>(opts: FetchHandlerOptions<...>): FetchHandler<...>
createFetchHandler({
BaseHandlerOptions<{ me: BuildProcedure<With<RootDef<{ headers: Headers; }, {}>, { ctx: { headers: Headers; }; ctxAdded: { user: { id: string; createdAt: Date; updatedAt: Date; email: string; emailVerified: boolean; name: string; image?: string | null | undefined | undefined; }; session: { ...; }; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>, "query", { ...; }>; }>.router: {
me: BuildProcedure<With<RootDef<{
headers: Headers;
}, {}>, {
ctx: {
headers: Headers;
};
ctxAdded: {
user: {
id: string;
createdAt: Date;
updatedAt: Date;
email: string;
emailVerified: boolean;
name: string;
image?: string | null | undefined | undefined;
};
session: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
expiresAt: Date;
token: string;
ipAddress?: string | null | undefined | undefined;
userAgent?: string | null | undefined | undefined;
};
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, "query", {
output: string;
errors: never;
services: never;
unmapped: never;
}>;
}
router,
createContext: (opts: CreateContextOpts<undefined>) => MaybePromise<{
headers: Headers;
}>
createContext: (opts: CreateContextOpts<undefined>opts) => ({
headers: Headersheaders: new var Headers: new (init?: HeadersInit) => HeadersHeaders(opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.request: HttpServerRequestrequest.HttpIncomingMessage<HttpServerError>.headers: Headersheaders as type Record<K extends keyof any, T> = { [P in K]: T; }Record<string, string>),
}),
});- Same-origin browsers send Better Auth's session cookie with each call. From another origin, set
httpLink({ init: () => ({ credentials: 'include' }) })and allow credentials in CORS. - Over WebSockets,
createContextgets the upgrade request's headers, so the cookie works there too. - Better Auth's own routes (
/api/auth/*) stay onauth.handler; serve them next to the tRPC endpoint.