# Differences Between Application Types in Logto: SPA, Traditional, Native, and M2M Explained

> Understand Logto application types like SPA, Traditional, Native, and M2M. Learn which OAuth flows and security profiles fit your app for seamless integration.

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

---

**Logto classifies client applications into four built-in types—SPA, Traditional, Native, and Machine-to-Machine (M2M)—each mapping to specific OAuth 2.0/OIDC flows, grant types, and security profiles.**

The `logto-io/logto` repository implements these distinct application types to enforce appropriate security boundaries for different deployment contexts. Each type determines whether the client is **confidential** (capable of storing secrets) or **public** (running in untrusted environments), which directly impacts the authentication flow and token handling strategy.

## Overview of the Four Application Types

Logto stores the application type in the `application_type` database enum, defined in schema alterations such as [`packages/schemas/alterations/1.0.0_beta.10-machine-to-machine-app.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/alterations/1.0.0_beta.10-machine-to-machine-app.ts) and [`packages/schemas/alterations/1.22.0-1731900596-add-saml-application-type.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/alterations/1.22.0-1731900596-add-saml-application-type.ts). This enum drives the validation logic in [`packages/core/src/models/application.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/models/application.ts), ensuring that each application adheres to type-specific constraints.

The four supported types are:

- **Traditional** – Server-side web applications (e.g., Next.js, Express)
- **SPA** (Single-Page Application) – Browser-based JavaScript applications (e.g., React, Vue)
- **Native** – Mobile and desktop applications (iOS, Android, Electron)
- **Machine-to-Machine** (M2M) – Backend services and CLI tools acting without user interaction

## Detailed Comparison of Logto Application Types

### Traditional Web Applications (Confidential Client)

**Traditional** applications represent server-rendered web apps where the backend can securely store credentials. According to the source code in [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts), these apps use the **Authorization Code** flow with a full server-side implementation.

- **OIDC Flow**: Authorization Code (confidential client)
- **Client Secret**: **Required** – Logto generates a confidential secret that must remain on the server
- **Redirect URI Pattern**: Full HTTPS URLs (e.g., `https://your-domain.com/callback`)
- **Refresh Token Handling**: Stored securely on the server; supports rotation and revocation

Because the code exchange happens server-to-server, Traditional apps can maintain long-lived sessions without exposing tokens to the browser.

### Single-Page Applications (SPA)

**SPA** applications run entirely in the browser and cannot keep secrets confidential. Logto enforces **PKCE** (Proof Key for Code Exchange) for all SPA apps, as implemented in the `pkceVerifier` validation step in [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts).

- **OIDC Flow**: Authorization Code + PKCE (public client)
- **Client Secret**: **Not required** – PKCE replaces the secret via the `code_challenge` mechanism
- **Redirect URI Pattern**: HTTPS URLs or scheme-only URLs that the browser can handle
- **Refresh Token Handling**: Stored in browser storage (`localStorage`/`sessionStorage`); Logto issues **short-lived access tokens** and **rotating refresh tokens** to mitigate XSS risks

The `tokenEndpointAuthMethod` is set to `"none"` for SPAs, explicitly preventing secret-based authentication at the token endpoint.

### Native Applications (Mobile and Desktop)

**Native** applications share the same security model as SPAs but operate outside browser environments. Mobile apps use the operating system's secure storage (iOS Keychain or Android Keystore) rather than browser storage.

- **OIDC Flow**: Authorization Code + PKCE (public client)
- **Client Secret**: **Not required** – PKCE is mandatory for Native apps
- **Redirect URI Pattern**: Custom URL schemes (`myapp://callback`) or HTTPS URIs registered with the OS
- **Refresh Token Handling**: Stored in device secure storage; Logto can enforce **token-binding** for additional security

Native apps utilize the same [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts) PKCE validation as SPAs, but the `application_type` distinction allows Logto to apply mobile-specific policies in the future.

### Machine-to-Machine (M2M) Applications

**Machine-to-Machine** apps represent non-interactive clients such as backend services, daemons, or CLI tools. These use the **Client Credentials** flow exclusively, as handled in [`packages/core/src/routes/token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/token.ts).

- **OIDC Flow**: Client Credentials (no user interaction)
- **Client Secret**: **Required** – The client authenticates using its secret or credentials
- **Redirect URI Pattern**: **Not applicable** – No redirect occurs in this flow
- **Refresh Token Handling**: **None** – M2M clients obtain new access tokens on demand by presenting their secret

M2M tokens typically have short time-to-live (TTL) values (default 5 minutes) because the client can request new tokens programmatically at any time.

## Security Architecture and Token Handling

### Grant Type Enforcement

Logto enforces grant type restrictions based on the `type` column in the `applications` table. The SQL constraint `check_application_type` (added in [`packages/schemas/alterations/1.22.0-1731900596-add-saml-application-type.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/alterations/1.22.0-1731900596-add-saml-application-type.ts)) ensures that:

- **SPA** and **Native** apps can only use `authorization_code`
- **Traditional** apps can use `authorization_code` and refresh tokens
- **M2M** apps are restricted to `client_credentials`

### Confidential vs. Public Clients

The fundamental architectural distinction lies in client classification:

- **Confidential** (Traditional, M2M): Can store secrets securely; participate in server-to-server authentication
- **Public** (SPA, Native): Cannot store secrets; rely on PKCE to prevent authorization code interception attacks

Logto automatically rejects token requests containing client secrets from public clients, as validated in the token endpoint implementation.

### Refresh Token Rotation

For public clients (SPA and Native), Logto implements **refresh token rotation** to prevent replay attacks. When a refresh token is used, Logto issues a new access token and a new refresh token, invalidating the old one. This mechanism is managed in [`packages/core/src/routes/token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/token.ts).

## Creating Applications via the Management API

The Management API (defined in [`packages/api/src/routes/applications.ts`](https://github.com/logto-io/logto/blob/main/packages/api/src/routes/applications.ts)) allows programmatic creation of applications. The request body must conform to the `Application` schema validation in [`packages/core/src/models/application.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/models/application.ts).

### Creating a SPA Application

```http
POST https://{your-logto-domain}/api/applications
Content-Type: application/json
Authorization: Bearer {admin-access-token}

{
  "name": "My React SPA",
  "type": "SPA",
  "redirectUris": ["https://my-spa.example.com/callback"],
  "postLogoutRedirectUris": ["https://my-spa.example.com/"],
  "grantTypes": ["authorization_code"],
  "tokenEndpointAuthMethod": "none"
}

```

Logto automatically enforces PKCE for this application type; you do not need to configure the `code_challenge_method` at creation time.

### Creating a Machine-to-Machine Application

```http
POST https://{your-logto-domain}/api/applications
Content-Type: application/json
Authorization: Bearer {admin-access-token}

{
  "name": "Backend Service",
  "type": "MachineToMachine",
  "clientSecret": true,
  "grantTypes": ["client_credentials"]
}

```

The response includes a `clientSecret` that you use to obtain access tokens:

```http
POST https://{your-logto-domain}/oidc/token
Content-Type: application/x-www-form-urlencoded

client_id={client_id}&client_secret={client_secret}&grant_type=client_credentials

```

### Native Application Configuration

Native apps follow the same pattern as SPAs but use custom URL schemes:

```http
POST https://{your-logto-domain}/api/applications
Content-Type: application/json
Authorization: Bearer {admin-access-token}

{
  "name": "iOS Mobile App",
  "type": "Native",
  "redirectUris": ["myapp://callback"],
  "grantTypes": ["authorization_code"],
  "tokenEndpointAuthMethod": "none"
}

```

When implementing the client, your Swift or Kotlin code must generate a PKCE code verifier and include the `code_challenge` in the authorization request, which Logto validates in [`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts).

## Summary

- **Traditional** apps are confidential server-side applications using Authorization Code flow with client secrets and server-stored refresh tokens.
- **SPA** apps are public browser applications using PKCE instead of secrets, with rotating refresh tokens stored in browser storage.
- **Native** apps are public mobile/desktop applications using PKCE with platform-specific secure storage for tokens.
- **M2M** apps are confidential non-interactive services using Client Credentials flow with short-lived tokens and no refresh mechanism.
- Logto enforces type-specific security constraints through database enums (defined in `packages/schemas/alterations`), model validation ([`packages/core/src/models/application.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/models/application.ts)), and route-level PKCE verification ([`packages/core/src/routes/oidc.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/oidc.ts)).

## Frequently Asked Questions

### Can I change an application's type after creation?

No, you cannot modify the `application_type` field after creation through the Management API. The database constraint `check_application_type` and the validation logic in [`packages/core/src/models/application.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/models/application.ts) treat the application type as immutable. If you need to switch types, you must create a new application with the correct configuration and migrate your client credentials.

### Why does Logto require PKCE for SPA and Native apps but not Traditional apps?

SPA and Native apps are **public clients** that cannot securely store a client secret, making them vulnerable to authorization code interception attacks. **PKCE** (Proof Key for Code Exchange) mitigates this by binding the authorization code to a cryptographically random verifier generated by the client. Traditional apps are **confidential clients** running on secure servers where the secret remains inaccessible to end users, so they rely on the client secret for authentication instead.

### How long do M2M access tokens last compared to SPA tokens?

According to the default configuration in [`packages/core/src/routes/token.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/token.ts), **M2M access tokens** typically have a short TTL of approximately **5 minutes** because the client can request new tokens on demand using its stored secret. **SPA access tokens** also have short lifetimes (often 1 hour), but these apps receive **refresh tokens** that rotate upon each use, allowing them to maintain sessions without storing long-lived credentials in browser storage.

### Can I use the Client Credentials grant with a Traditional web application?

While the database schema technically supports multiple grant types, Logto's `check_application_type` constraint restricts **Traditional** apps to the Authorization Code flow. If you need Client Credentials functionality for a backend service, you must create a dedicated **Machine-to-Machine** application. This separation ensures that user-facing applications and service-to-service authentication follow distinct security policies and audit trails.