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

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 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) 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.

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.

The Refresh Token Grant Implementation

The core refresh logic resides in 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.

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, 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:

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:

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):

{
  "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:

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.
  • Refresh tokens are issued when requesting the offline_access scope, with configurable TTL via refreshTokenTtlInDays in packages/schemas/src/consts/oidc.ts.
  • The refresh-token grant implementation in 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 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.

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 (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 between lines 96-138.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →