# How Logto Handles User Sessions and Token Refresh: OIDC Implementation Guide

> Learn how Logto manages user sessions with sign-in context and implements OIDC token refresh. Explore custom grants, validation, and token rotation for secure access.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: deep-dive
- Published: 2026-07-05

---

**Logto tracks user sessions by capturing sign-in context data (IP, user-agent, location) stored in `lastSubmission.signInContext`, while token refresh operates through a custom refresh-token grant in [`packages/core/src/oidc/grants/refresh-token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/grants/refresh-token.ts) that validates tokens, checks organization scopes, and optionally rotates refresh tokens when exchanging them for new access tokens.**

Logto, an open-source identity and access management platform, provides enterprise-grade session management and token lifecycle handling through its OIDC-compliant architecture. Understanding how Logto manages user sessions and performs token refresh is essential for developers building secure authentication flows. This article examines the source code implementation in the `logto-io/logto` repository to explain the technical mechanisms behind session tracking and token rotation.

## Session Tracking and Context Capture in Logto

Logto records comprehensive metadata every time a user authenticates. This data enables administrators to review active sessions through the Logto admin console while maintaining security standards.

### Capturing Sign-In Context Data

When a user authenticates, Logto records a **sign-in context** containing the IP address, user-agent string, city, and country. This raw data resides in `SessionWithLastSubmission.lastSubmission.signInContext` and persists with the user's session record for administrative visibility and audit trails.

### Parsing Device Information with ua-parser-js

Logto utilizes the `UAParser` library to transform raw user-agent strings into structured device information. The `getParsedUserAgentInfo` function (lines 66-78 in [`packages/shared/src/utils/session.ts`](https://github.com/logto-io/logto/blob/main/packages/shared/src/utils/session.ts)) extracts the browser name, operating system, and device model from the raw string.

### Formatting Session Display Information

The `getSessionDisplayInfo` utility converts raw session data into human-readable formats for the admin UI. The `formatSessionDeviceName` function (lines 81-98) generates descriptive strings like "Chrome on macOS", while `formatSessionLocation` (lines 101-105) creates location strings such as "Taipei, Taiwan". The complete implementation lives in **[`packages/shared/src/utils/session.ts`](https://github.com/logto-io/logto/blob/main/packages/shared/src/utils/session.ts)**.

## Logto Token Refresh Architecture

Logto extends the standard OIDC provider with custom logic for refresh token handling, supporting long-lived sessions for native and single-page applications.

### Issuing Refresh Tokens with offline_access

Logto issues long-lived **refresh tokens** when clients request the `offline_access` scope during the initial authentication request. The token's time-to-live is configurable via the **`refreshTokenTtlInDays`** constant defined in [`packages/schemas/src/consts/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/consts/oidc.ts).

### The Refresh Token Grant Implementation

The core refresh logic resides in **[`packages/core/src/oidc/grants/refresh-token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/grants/refresh-token.ts)**. This grant handler extends the standard `node-oidc-provider` to include Logto-specific validations such as organization scope verification and application access checks. Exhaustive tests confirming correct handling of missing, expired, and rotated tokens exist in **[`packages/core/src/oidc/grants/refresh-token.test.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/grants/refresh-token.test.ts)**.

### Validation and Security Checks

Before issuing new tokens, Logto validates the refresh token's existence, client binding, expiration status, and consumption state. The implementation checks these security conditions between lines 96-118 and 121-138 in [`refresh-token.ts`](https://github.com/logto-io/logto/blob/main/refresh-token.ts), ensuring the user still has application access and that required scopes (including organization scopes) are present.

### Token Rotation Strategy

When **`rotateRefreshToken`** is enabled in the provider configuration, Logto consumes the old token and issues a new one upon each refresh request. This rotation logic appears in lines 84-115 and 180-192 of the grant implementation, where `refreshToken.consume()` marks the token as used before generating cryptographically secure replacements.

### Access Token and Response Generation

The grant handler creates new access tokens (and organization tokens when applicable) based on scopes stored in the refresh token. The final response follows the OIDC standard with `access_token`, `expires_in`, optional `id_token`, optional `refresh_token`, `scope`, and `token_type` (lines 200-274).

## Practical Implementation Examples

### Retrieving Session Display Information

Use the `getSessionDisplayInfo` utility to format raw session data for your admin interface:

```typescript
import { getSessionDisplayInfo } from '@logto/shared/utils';

// Assume `session` comes from the admin-console query API
const displayInfo = getSessionDisplayInfo(session);

console.log(displayInfo);
/*
{
  name: "Chrome on macOS",
  location: "Taipei, Taiwan",
  ip: "203.0.113.42",
  city: "Taipei",
  country: "Taiwan",
  browserName: "Chrome",
  osName: "macOS",
  deviceModel: undefined
}
*/

```

### Exchanging Refresh Tokens for Access Tokens

Request new tokens using the standard OIDC token endpoint:

```bash
curl -X POST https://YOUR_LOGTO_DOMAIN/oidc/token \
  -d "grant_type=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "refresh_token=REFRESH_TOKEN_OBTAINED_EARLIER"

```

Typical JSON response (mirroring the shape built in [`refresh-token.ts`](https://github.com/logto-io/logto/blob/main/refresh-token.ts)):

```json
{
  "access_token": "eyJhbGciOiJ... (JWT)",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJ... (JWT)",
  "refresh_token": "newRefreshTokenIfRotated",
  "scope": "openid profile email",
  "token_type": "Bearer"
}

```

### Enabling Token Rotation

Configure the OIDC provider to rotate refresh tokens on each use:

```typescript
import { Provider } from 'oidc-provider';

const provider = new Provider('https://YOUR_LOGTO_DOMAIN', {
  rotateRefreshToken: true,
  // ... other configuration
});

```

## Summary

- Logto captures comprehensive sign-in context (IP, user-agent, location) stored in `lastSubmission.signInContext` and formats it via `getSessionDisplayInfo` in [`packages/shared/src/utils/session.ts`](https://github.com/logto-io/logto/blob/main/packages/shared/src/utils/session.ts).
- Refresh tokens are issued when requesting the `offline_access` scope, with configurable TTL via `refreshTokenTtlInDays` in [`packages/schemas/src/consts/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/consts/oidc.ts).
- The refresh-token grant implementation in [`packages/core/src/oidc/grants/refresh-token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/grants/refresh-token.ts) validates tokens, checks organization scopes, and handles rotation.
- Token rotation consumes old tokens and issues new ones when `rotateRefreshToken` is enabled, calling `refreshToken.consume()` to prevent replay attacks.
- The grant returns standard OIDC responses including access tokens, ID tokens, and refresh tokens with appropriate expiration metadata.

## Frequently Asked Questions

### How does Logto store session metadata like IP address and device information?

Logto stores session metadata in the `lastSubmission.signInContext` property of the session object. When a user authenticates, the system captures the IP address, user-agent string, city, and country from the request. The `getSessionDisplayInfo` function in [`packages/shared/src/utils/session.ts`](https://github.com/logto-io/logto/blob/main/packages/shared/src/utils/session.ts) then parses this data using `ua-parser-js` to create human-readable device names and location strings for display in the admin console.

### What scope is required to obtain a refresh token in Logto?

Clients must request the **`offline_access`** scope during the initial authentication request to receive a refresh token. Without this scope, Logto issues only access tokens and ID tokens. The refresh token's lifetime is controlled by the **`refreshTokenTtlInDays`** configuration constant found in [`packages/schemas/src/consts/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/consts/oidc.ts).

### How does Logto handle refresh token rotation?

When `rotateRefreshToken` is enabled in the OIDC provider configuration, Logto invalidates the used refresh token by calling `refreshToken.consume()` and issues a new refresh token in the response. This implementation in [`packages/core/src/oidc/grants/refresh-token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/grants/refresh-token.ts) (lines 84-115) ensures that tokens cannot be reused, mitigating the risk of token theft and replay attacks.

### What validations does Logto perform during a token refresh request?

Logto validates that the refresh token exists, matches the requesting client ID, has not expired, and has not been previously consumed. Additionally, the grant handler verifies that the user still has access to the application and checks for required organization scopes. These validations occur in [`packages/core/src/oidc/grants/refresh-token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/oidc/grants/refresh-token.ts) between lines 96-138.