Differences Between Application Types in Logto: SPA, Traditional, Native, and M2M Explained
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 and 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, 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, 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.
- OIDC Flow: Authorization Code + PKCE (public client)
- Client Secret: Not required – PKCE replaces the secret via the
code_challengemechanism - 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 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.
- 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) ensures that:
- SPA and Native apps can only use
authorization_code - Traditional apps can use
authorization_codeand 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.
Creating Applications via the Management API
The Management API (defined in 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.
Creating a SPA Application
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
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:
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:
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.
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), and route-level PKCE verification (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 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, 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →