# How the Logto Consent Flow Works: OAuth 2.0 and OpenID Connect Implementation

> Explore the Logto consent flow, a precise implementation of OAuth 2.0 and OpenID Connect. Understand how Logto manages user permissions and data access with clear layers for consent handling.

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

---

**Logto implements the OAuth 2.0/OpenID Connect consent flow through three coordinated layers: the OIDC core in [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts) checks for existing consent and parses `prompt=consent` parameters, the Consent API in [`packages/experience/src/apis/consent.ts`](https://github.com/logto-io/logto/blob/main/packages/experience/src/apis/consent.ts) handles data retrieval and persistence via `GET` and `POST /api/consent` endpoints, and the Experience SPA renders the consent interface using the `ConsentInfoResponse` schema from [`packages/schemas/src/types/consent.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/consent.ts).**

The consent flow ensures users explicitly authorize applications to access their data. In the logto-io/logto repository, this security requirement is implemented through a strict sequence of API validations, database transactions, and UI components that manage the `application_user_consent_*` tables.

## Logto Consent Flow Architecture

Logto's consent implementation spans the authorization server, backend APIs, and frontend experience. When a client application initiates an OAuth flow with `prompt=consent`, the system executes a seven-step process to verify, display, record, and persist user authorization decisions.

### 1. Authorization Request Parsing

The flow begins at the OIDC endpoint. In [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts), the authorization server parses incoming `/oidc/authorize` requests and extracts critical parameters including `scope`, `prompt`, and `redirect_uri`.

```typescript
// Conceptual implementation based on packages/core/src/routes/oidc.ts
const handleAuthorize = async (ctx) => {
  const { prompt, scope } = ctx.query;
  
  if (prompt === 'consent' || !hasExistingConsent(userId, applicationId, scope)) {
    return redirectToConsentPage(userId, applicationId);
  }
};

```

### 2. Consent Requirement Verification

The core checks whether the user has previously granted consent for the requested scopes. If the user has not consented, or if the client explicitly requests consent via the `prompt` parameter, the flow proceeds to the Consent API.

### 3. Consent Data Retrieval

The Experience SPA fetches consent context through `getConsentInfo()` defined in [`packages/experience/src/apis/consent.ts`](https://github.com/logto-io/logto/blob/main/packages/experience/src/apis/consent.ts). This function calls `GET /api/consent/:userId/:applicationId` to retrieve application metadata and required scopes.

```typescript
// packages/experience/src/apis/consent.ts
export const getConsentInfo = async () => {
  return api.get('/api/consent/:userId/:applicationId').json<ConsentInfoResponse>();
};

```

### 4. Consent UI Rendering

The consent page component in [`packages/experience/src/pages/Consent/index.tsx`](https://github.com/logto-io/logto/blob/main/packages/experience/src/pages/Consent/index.tsx) consumes the `ConsentInfoResponse` data to render the application name, logo, and scope descriptions. This component validates the response against `consentInfoResponseGuard` from [`packages/schemas/src/types/consent.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/consent.ts).

### 5. User Decision Processing

When the user clicks **Allow**, the frontend invokes the `consent()` function, which sends a `POST /api/consent` request with the granted scopes.

```typescript
// packages/experience/src/apis/consent.ts
export const consent = async (grantedScopes: string[]) => {
  return api.post('/api/consent', { 
    json: { scopes: grantedScopes } 
  });
};

```

### 6. Database Persistence

The API handler updates the `application_user_consent_*` tables (defined in migration files under `packages/schemas/alterations/`). The `consentInfoResponseGuard` validates all payloads before persistence, ensuring data integrity across the consent records.

```typescript
// packages/schemas/src/types/consent.ts
export const consentInfoResponseGuard = z.object({
  applicationName: z.string(),
  applicationLogo: z.string().optional(),
  scopes: z.array(z.object({
    name: z.string(),
    description: z.string()
  })),
  redirectUri: z.string()
});

```

### 7. OIDC Flow Completion

After persisting the consent record, the API redirects the user to the original `redirect_uri` with an authorization `code`. The OIDC core then processes the token exchange at `/oidc/token` to complete the flow.

## Edge Cases and Special Behaviors

**Device Flow Bypass**: Device authorization flows do not require interactive UI consent. As implemented in [`packages/schemas/src/foundations/jsonb-types/oidc-module.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/foundations/jsonb-types/oidc-module.ts), the core automatically stores consent for device flows without redirecting to the consent page.

**Refresh Token Optimization**: When `prompt=consent` is present but the user has already granted the requested scopes, Logto skips the UI and issues a new refresh token directly, optimizing the user experience while maintaining security compliance.

**Scope Change Detection**: If an application requests scopes not covered by existing consent records, Logto automatically re-prompts the user. This ensures that expanding application permissions always requires explicit user approval.

## Summary

- **Entry Point**: The OIDC handler in [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts) initiates consent checks during authorization requests
- **API Layer**: [`packages/experience/src/apis/consent.ts`](https://github.com/logto-io/logto/blob/main/packages/experience/src/apis/consent.ts) provides `getConsentInfo()` and `consent()` for data retrieval and submission
- **Schema Validation**: [`packages/schemas/src/types/consent.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/consent.ts) defines `ConsentInfoResponse` and `consentInfoResponseGuard` for type safety
- **Persistence**: Consent records are stored in `application_user_consent_*` tables via migrations in `packages/schemas/alterations/`
- **Edge Handling**: Device flows bypass UI consent, while scope changes trigger mandatory re-authorization

## Frequently Asked Questions

### What triggers the consent screen in Logto?

The consent screen appears when a client sends an authorization request with `prompt=consent`, or when the user has not previously granted consent for the requested scopes. The OIDC core in [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts) checks existing consent records against the requested scopes before deciding to redirect to the consent endpoint.

### How does Logto store user consent decisions?

Logto persists consent decisions in the `application_user_consent_*` tables, which are created through migration files in `packages/schemas/alterations/`. When a user approves consent, the `POST /api/consent` endpoint updates these tables with the granted scopes and timestamps, validated by the `consentInfoResponseGuard` schema.

### Does Logto support consent for device authorization flows?

Yes, but with a significant difference. According to the implementation in [`packages/schemas/src/foundations/jsonb-types/oidc-module.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/foundations/jsonb-types/oidc-module.ts), device flows do not render the interactive consent UI. Instead, the core automatically stores consent records internally, bypassing the [`packages/experience/src/pages/Consent/index.tsx`](https://github.com/logto-io/logto/blob/main/packages/experience/src/pages/Consent/index.tsx) interface while maintaining audit trails.

### What happens when an application requests new scopes?

Logto performs a scope comparison against existing `application_user_consent_*` records. If the authorization request contains scopes not present in the stored consent, the system automatically triggers the full consent flow again, requiring the user to explicitly approve the new permissions before proceeding.