Skip to content
tRPC

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, createContext gets the upgrade request's headers, so the cookie works there too.
  • Better Auth's own routes (/api/auth/*) stay on auth.handler; serve them next to the tRPC endpoint.