# Managing User Sessions and OIDC Session Extensions with Logto: A Complete Guide

> Master OIDC session extensions with Logto. Learn how Logto enhances standard OIDC sessions using a dedicated table for comprehensive session management and token customization via the Management API.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-07-04

---

**Logto extends standard OIDC sessions using a dedicated `oidc_session_extensions` table to persist interaction data like `lastSubmission` across authentication flows, enabling rich token customization and comprehensive session management through the Management API.**

The `logto-io/logto` repository implements a sophisticated two-layer session architecture that separates core OpenID Connect state from Logto-specific extensions. While standard OIDC session data lives in `oidc_model_instances`, Logto stores supplementary context—such as consent interaction details and client identifiers—in a parallel `oidc_session_extensions` table. This design allows the platform to maintain strict OIDC compliance while supporting advanced features like custom JWT claims based on sign-in context.

## Understanding the OIDC Session Extensions Architecture

### The Database Schema

Logto creates the `oidc_session_extensions` table through a database alteration script introduced in version 1.29.0. This table links extension data to core sessions via a composite primary key while storing JSON-based interaction context.

```typescript
// packages/schemas/alterations/1.29.0-1749026308-add-oidc-session-extension-table.ts
await pool.query(sql`
  create table oidc_session_extensions (
    tenant_id varchar(21) not null references tenants (id) on update cascade on delete cascade,
    session_uid varchar(128) not null,
    account_id varchar(12) not null references users (id) on update cascade on delete cascade,
    last_submission jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default(now()),
    updated_at timestamptz not null default(now()),
    primary key (tenant_id, session_uid)
  );
`);

```

The schema stores critical fields including `session_uid` (linking to the OIDC session), `account_id` (referencing the user), and `last_submission` (containing the interaction context as JSONB). This structure enables Logto to persist complex sign-in metadata—such as IP addresses and user agents—beyond the standard OIDC session lifetime.

### Row-Level Security and Automated Updates

The alteration applies `applyTableRls` for row-level security enforcement and establishes a trigger that automatically updates the `updated_at` timestamp on every modification. This ensures tenant isolation and accurate audit tracking across multi-tenant deployments.

## Querying Session Extensions with OidcSessionExtensionsQueries

All database operations for session extensions are encapsulated in the `OidcSessionExtensionsQueries` class located at [`packages/core/src/queries/oidc-session-extensions.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/queries/oidc-session-extensions.ts). This abstraction layer provides type-safe access to the extensions table with built-in conflict resolution.

### Insert with Conflict Resolution

The `insert` method uses `buildInsertIntoWithPool` to handle upsert operations, automatically updating fields when a session extension already exists:

```typescript
// packages/core/src/queries/oidc-session-extensions.ts
public readonly insert = buildInsertIntoWithPool(this.pool)(OidcSessionExtensions, {
  onConflict: {
    fields: [fields.tenantId, fields.sessionUid],
    setExcludedFields: [
      fields.lastSubmission,
      fields.updatedAt,
      fields.accountId,
      fields.clientId,
    ],
  },
  returning: true,
});

```

This approach ensures that `lastSubmission`, `accountId`, and `clientId` remain synchronized with the latest interaction data while preserving the original creation timestamp.

### Retrieval and Deletion Methods

The query class exposes several specialized methods for session management:

- **`findBySessionUid(sessionUid: string)`** – Retrieves extension data for a specific OIDC session
- **`findUserActiveSessionsWithExtensions(accountId: string)`** – Lists all active sessions with their extensions for a given user
- **`findUserActiveSessionWithExtension(accountId: string, sessionUid: string)`** – Fetches a specific session with its extension data
- **`deleteBySessionUid(sessionUid: string)`** – Removes extension data when a session terminates
- **`deleteByAccountId(accountId: string)`** – Cleans up all extensions for a user (useful for account deletion)

## Enriching Sessions in the Session Library

The high-level session API transforms raw database records into structured objects suitable for the Management API and token customization workflows.

### Formatting Session Data

Located in [`packages/core/src/libraries/session/index.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/session/index.ts), the `formatSessionWithExtension` function validates and structures the combined OIDC payload and extension data:

```typescript
// packages/core/src/libraries/session/index.ts
const formatSessionWithExtension = (session: SessionInstanceWithExtension) => {
  const { lastSubmission, clientId, accountId, payload, ...rest } = session;

  const interactionContextResult =
    jwtCustomizerUserInteractionContextGuard.safeParse(lastSubmission);

  const payloadResult = oidcSessionInstancePayloadGuard.safeParse(payload);
  if (!payloadResult.success) {
    throw new RequestError({ code: 'oidc.invalid_session_payload', status: 500 });
  }

  return {
    ...rest,
    payload: payloadResult.data,
    lastSubmission: interactionContextResult.success ? interactionContextResult.data : null,
    clientId,
    accountId,
  };
};

```

This function applies runtime validation using Zod guards (`jwtCustomizerUserInteractionContextGuard` and `oidcSessionInstancePayloadGuard`) to ensure type safety before exposing data to downstream consumers.

### Public API Types

The Management API returns standardized session objects defined in [`packages/schemas/src/types/user-sessions.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/user-sessions.ts):

```typescript
// packages/schemas/src/types/user-sessions.ts
export const userExtendedSessionGuard = z.object({
  payload: oidcSessionInstancePayloadGuard,
  lastSubmission: jwtCustomizerUserInteractionContextGuard.nullable(),
  clientId: z.string().nullable(),
  accountId: z.string().nullable(),
  expiresAt: z.number(),
});

```

These types export as `GetUserSessionsResponse` and `GetUserSessionResponse`, powering the `GET /users/:userId/sessions` and `GET /users/:userId/sessions/:sessionId` endpoints.

## Persisting Interaction Data During Consent

When users complete authentication or consent flows, Logto captures the interaction context and persists it to the extensions table before destroying the transient OIDC interaction object.

### The Consent Flow Implementation

The `saveInteractionLastSubmissionToSession` function in [`packages/core/src/libraries/session/consent.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/libraries/session/consent.ts) handles this persistence:

```typescript
// packages/core/src/libraries/session/consent.ts
const saveInteractionLastSubmissionToSession = async (
  queries: Queries,
  interactionDetails: Awaited<ReturnType<Provider['interactionDetails']>>
) => {
  const { session, lastSubmission, params: { client_id: clientId } } = interactionDetails;
  if (!session || !lastSubmission) {
    return;
  }

  const { oidcSessionExtensions } = queries;
  const result = jsonObjectGuard.safeParse(lastSubmission);
  if (result.success) {
    await oidcSessionExtensions.insert({
      sessionUid: session.uid,
      accountId: session.accountId,
      lastSubmission: result.data,
      ...conditional(typeof clientId === 'string' && { clientId }),
    });
  }
};

```

This function executes during the consent phase, ensuring that sign-in context (IP address, user agent, authentication methods) survives the transition from the temporary interaction to the persistent session, enabling later retrieval during token generation.

## Accessing Session Data for Token Customization

Logto exposes stored interaction data to JWT customization scripts, allowing developers to inject sign-in context into access tokens and ID tokens.

### Reading Extensions for JWT Claims

The `getInteractionLastSubmission` function in [`packages/core/src/oidc/extra-token-claims.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/extra-token-claims.ts) retrieves the stored context:

```typescript
// packages/core/src/oidc/extra-token-claims.ts
const getInteractionLastSubmission = async (
  queries: Queries,
  { accountId, sessionUid }: AccessToken
) => {
  const { oidcSessionExtensions } = queries;
  const sessionExtension = await oidcSessionExtensions.findBySessionUid(sessionUid);
  if (!sessionExtension || sessionExtension.accountId !== accountId) {
    return;
  }

  const { lastSubmission } = sessionExtension;
  const interactionData = jwtCustomizerUserInteractionContextGuard.safeParse(lastSubmission);
  if (!interactionData.success) {
    return;
  }

  return interactionData.data;
};

```

This verification step ensures that the session belongs to the requesting user before exposing potentially sensitive interaction metadata.

### Implementation in Custom Scripts

When building extra token claims, Logto injects the interaction data into the customization context. Developers access this data in scripts stored via the Logto Admin UI:

```javascript
// Example JWT customiser script
exports = async (payload, context) => {
  // context.interaction contains the parsed lastSubmission
  const { ip, userAgent } = context.interaction ?? {};
  
  return {
    ...payload,
    client_ip: ip,
    auth_context: userAgent
  };
};

```

This capability enables security policies that embed device fingerprinting or network context directly into tokens issued by the Logto OIDC provider.

## Practical Implementation Examples

### Retrieving Active Sessions via Node SDK

To fetch a user's sessions with their extensions using the session library:

```typescript
import { createSessionLibrary } from '@logto/core/src/libraries/session/index.js';
import Queries from '@logto/core/src/tenants/Queries.js';

// Initialize with tenant-specific queries
const sessionLib = createSessionLibrary(queries);

// Retrieve all active sessions with extension data
const sessions = await sessionLib.findUserActiveSessionsWithExtensions(userId);

// sessions contains: payload, lastSubmission, clientId, accountId, expiresAt
console.log(sessions[0]?.lastSubmission?.ip);

```

### Revoking Session Grants Programmatically

To revoke all grants associated with a session (e.g., during logout):

```typescript
import { SessionGrantRevokeTarget } from '@logto/schemas';

await sessionLib.revokeSessionAssociatedGrants({
  provider,
  authorizations: session.authorizations,
  target: SessionGrantRevokeTarget.All,
});

```

This triggers the underlying `oidc-provider` to invalidate refresh tokens and access tokens linked to the session authorizations.

### Accessing Interaction Context in Management APIs

When calling `GET /users/:userId/sessions`, the response includes the structured extension data:

```json
{
  "id": "session-uid-here",
  "payload": { /* OIDC session payload */ },
  "lastSubmission": {
    "ip": "203.0.113.42",
    "userAgent": "Mozilla/5.0...",
    "authMethods": ["password", "totp"]
  },
  "clientId": "my-app",
  "accountId": "user123",
  "expiresAt": 1699123456
}

```

## Summary

- **Logto uses a dual-table architecture** separating core OIDC sessions (`oidc_model_instances`) from Logto-specific extensions (`oidc_session_extensions`) to maintain compliance while enabling rich features.
- **The `oidc_session_extensions` table** stores `lastSubmission` data, `clientId`, and `accountId` with row-level security and automatic timestamp updates.
- **`OidcSessionExtensionsQueries`** provides type-safe CRUD operations with PostgreSQL conflict resolution to handle session updates idempotently.
- **The session library** validates and formats raw database records into `userExtendedSessionGuard` structures for the Management API.
- **Consent flow persistence** captures interaction context before destruction, making sign-in metadata available for the session lifetime.
- **Token customization** retrieves stored interaction data via `getInteractionLastSubmission`, injecting security context into JWT claims through the `context.interaction` object.

## Frequently Asked Questions

### How does Logto separate standard OIDC data from custom session extensions?

Logto stores standard OIDC session parameters in `oidc_model_instances` (managed by the underlying `oidc-provider` library) while maintaining a parallel `oidc_session_extensions` table for Logto-specific data. This separation allows the platform to remain compliant with OIDC specifications while supporting extensions like interaction context and client metadata. The two tables link via the `session_uid` field, with the extensions table enforcing foreign key constraints to both the `tenants` and `users` tables.

### What data is stored in the `lastSubmission` field of a session extension?

The `lastSubmission` field contains a JSONB object captured during the authentication or consent interaction, typically including the user's IP address, user agent string, authentication methods used (e.g., password, TOTP), and other sign-in context. According to the schema in [`packages/schemas/src/types/user-sessions.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/user-sessions.ts), this data validates against `jwtCustomizerUserInteractionContextGuard` and becomes accessible in JWT customization scripts under `context.interaction`.

### Can I query session extensions directly for a specific user?

Yes, the `OidcSessionExtensionsQueries` class exposes `findUserActiveSessionsWithExtensions(accountId)`, which returns all active sessions for a user joined with their extension data. This method powers the Management API endpoints `GET /users/:userId/sessions` and returns validated objects conforming to `userExtendedSessionGuard`, including the parsed `lastSubmission`, `clientId`, and expiration timestamps.

### How do I access session interaction data when customizing JWT claims?

Within a custom JWT script (configured in the Logto Admin Console), interaction data is available via the `context.interaction` object. This data is retrieved internally by the `getInteractionLastSubmission` function in [`packages/core/src/oidc/extra-token-claims.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/extra-token-claims.ts), which validates the session ownership and parses the stored JSONB before injection. You can access fields like `context.interaction.ip` or `context.interaction.userAgent` to add location-based or device-based claims to issued tokens.