---
title: Read sessions
description: Read the request's verified identity in a business function, handle anonymous callers, and understand session types and non-HTTP limits.
pagination: false
sidebar:
  icon: key-round
---

Read the current caller's session inside a function with `await context.auth.getSession()`. It returns the Better Auth session result or `null`. The result contains both the session record and
the user; the verified user ID is `session.user.id`, not a user-supplied request field.

> **Before you begin**
>
> Before using this guide, complete [Better Auth installation and
> configuration](/docs/modules/auth/better-auth) and apply the database migrations. The minimal app
> does not include the optional auth SDK.

## Read an optional session

The example's account operation supports anonymous and authenticated requests:

```ts title="src/services/account/functions/session.function.ts"
/** Reads the invocation's native session and returns validated authentication and user identity fields. */
import { defineFunction } from "@relkit/app/functions";
import { z } from "@relkit/app/schema";

export default defineFunction({
  input: z.object({}),
  output: z.object({ authenticated: z.boolean(), userId: z.string().optional() }),
  handler: async (_input, context) => {
    const session = await context.auth.getSession();
    return session === null
      ? { authenticated: false }
      : { authenticated: true, userId: session.user.id };
  },
});
```

Its output deliberately returns only an authentication flag and user ID. Do not return the complete
session object simply because it is available: auth records can contain tokens, IP addresses, and
other private data. Define the response your caller needs.

The compiler generates auth context types from the registered service's handler. Functions do not add
a `dependencies.auth` entry, import a global auth instance, or open a database connection. Run
`bun run check` after changing registration or type-affecting auth options, then `bun run typecheck`.
Inspect `.relkit/generated/context-registry.d.ts` if the expected auth type is missing; do not edit it.

## Choose public or authenticated access

The operation is registered by an ordinary domain service:

```ts title="src/services/account/service.ts"
/** Exposes native session observation as the account boundary used by profile and session routes. */
import { defineService } from "@relkit/app/services";
import session from "./functions/session.function.js";

export default defineService({ functions: { session } });
```

Its public route is:

```ts title="src/routes/session/route.ts"
/** Maps session GET requests to account session observation, including the explicit anonymous result. */
import { defineRoute } from "@relkit/app/routes";
import account from "@app/services/account/service.js";

export const GET = defineRoute({ target: account.session });
```

`GET /session` returns HTTP `200` with `{ "authenticated": false }` when signed out. The same function
is also mounted at `/account/profile`, which the auth mount protects; there, an anonymous request
receives `401` before the function runs. See [Protect routes](/docs/modules/auth/protect-routes).

Even behind a protected route, keep function behavior safe for a missing session. That function may
later have another caller or transport. For operations that require identity, reject a missing
session using an application [declared error](/docs/learn/concepts/errors), then verify permission before
reading or modifying protected records. A non-null session is not a role or ownership check.

## Understand request-scoped lookup

Session resolution uses the incoming request headers. It is lazy: a public operation that never
calls `getSession()` does not need that lookup. The HTTP runtime memoizes the lookup promise for the
request, including the lookup used by route protection. Concurrent reads within that request share
the result; this is not an application-wide identity cache.

Do not save the session in module state or reuse it for another request. A lookup already completed
in a request is not a fresh authorization check after revocation or account changes within that same
request. Use separate requests to verify lifecycle changes such as sign-out.

`context.auth.getSession()` returns data, not a native HTTP `Response`. Do not assume it forwards
Better Auth's `Set-Cookie` headers to the browser. Use the native [auth endpoints and client](/docs/modules/auth/clients)
for sign-in, sign-out, and session lifecycle flows.

## Work without an HTTP request

Jobs and direct invocations do not acquire a browser session merely because an auth service exists.
Without an active HTTP auth context, session lookup resolves to `null`. The Inspector is not a way
to impersonate a signed-in browser by supplying a `userId` input.

For deferred work, authorize the enqueue operation, record the relevant actor/resource identifiers
in the job's validated input, and define which permissions must be rechecked at execution time.
Do not put cookies, passwords, or session tokens in queued payloads. A client-supplied actor ID is
untrusted until the authenticated entry point validates it.

For unit tests, inject an explicit auth context; for request behavior, use the real HTTP harness in
[Auth testing](/docs/modules/auth/testing). Neither approach requires reading a developer's browser cookies.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/better-auth` API](/docs/api/better-auth)
- [`@relkit/functions` API](/docs/api/functions)

- [`examples/auth-drizzle/src/services/account/functions/session.function.ts`](https://github.com/rel-kit/relkit/blob/main/examples/auth-drizzle/src/services/account/functions/session.function.ts)
