Clients
Learn how Clients represent your applications and enable OAuth 2.0/OIDC authentication flows.
Last updated 2026-07-01
A Client represents an application that uses AuthPI for authentication. In OAuth 2.0 and OpenID Connect terminology, clients are the “relying parties”—the applications that rely on AuthPI to authenticate users and issue tokens.
Every application that needs to authenticate users through AuthPI must be registered as a Client. This includes your web applications, mobile apps, single-page applications, and backend services.
Why Register Clients?
Registering clients serves several purposes:
Security: AuthPI needs to know which applications are legitimate. Without client registration, any application could pretend to be yours and trick users into authenticating.
Configuration: Different applications have different needs. A mobile app handles authentication differently than a web server. Client registration lets you configure the right settings for each application.
Access control: Clients specify which scopes (permissions) they can request and which redirect URIs are valid. This limits what each application can do.
Audit trail: Tokens include the client ID, so you can track which application requested access.
Client Types
AuthPI supports four application types, each designed for different architectures:
Web Applications
Traditional server-side web applications where the backend handles authentication. The server can securely store secrets because the code runs in a controlled environment.
Examples: Node.js/Express apps, Django, Rails, PHP applications, server-rendered React/Next.js
Characteristics:
- Backend exchanges authorization codes for tokens
- Can securely store client secrets
- Redirect URIs are server endpoints (HTTPS URLs)
- Sessions typically managed server-side
When to use: Your application has a backend server that handles authentication. Users interact with server-rendered pages or the frontend communicates through your backend.
Single-Page Applications (SPA)
JavaScript applications that run entirely in the browser. The code is public (anyone can view it in browser dev tools), so these clients cannot securely store secrets.
Examples: React, Vue, Angular, Svelte applications running in the browser
Characteristics:
- All code runs in the browser (public)
- Cannot store client secrets securely
- Must use PKCE for security
- Redirect URIs are frontend routes
- Tokens stored in browser (memory or secure storage)
When to use: Your frontend is a standalone JavaScript application that directly handles authentication without a backend intermediary.
Trade-offs: SPAs are more exposed to certain attacks (XSS can steal tokens). Consider using a Backend-for-Frontend (BFF) pattern for sensitive applications—have your backend handle token exchange and store tokens server-side, giving the frontend only a session cookie.
Native Applications
Mobile apps (iOS, Android) and desktop applications. Like SPAs, native apps are distributed to users and cannot securely store secrets in the binary.
Examples: iOS apps, Android apps, Electron desktop apps, CLI tools
Characteristics:
- App binary is distributed to users (public)
- Cannot store client secrets securely
- Must use PKCE for security
- Redirect URIs use custom schemes (e.g.,
myapp://callback) or claimed HTTPS URLs - Handles authentication via system browser or embedded WebView
When to use: You’re building a mobile or desktop application that needs user authentication.
Trade-offs: Native apps should use the system browser (not embedded WebViews) for authentication to benefit from existing browser sessions and prevent the app from seeing user credentials directly.
Machine-to-Machine (M2M)
Backend services that authenticate themselves without user interaction. These are confidential clients that use their credentials directly to obtain tokens.
Examples: Microservices, cron jobs, data pipelines, backend integrations
Characteristics:
- No user interaction—service authenticates itself
- Uses client credentials grant (not authorization code)
- Must securely store client secret
- No redirect URIs (no browser involved)
- Tokens represent the service, not a user
When to use: A backend service needs to call APIs without acting on behalf of a specific user. For example, a nightly job that processes all users’ data, or a microservice that needs to authenticate to another service.
Trade-offs: M2M tokens don’t represent users, so you can’t use them for user-specific operations. If your service acts on behalf of users, use the authorization code flow instead.
Confidential vs. Public Clients
The most important distinction between client types is whether they can securely store secrets:
Confidential Clients
Can securely store a client secret because the code runs in a controlled environment (your servers).
Types: Web applications, M2M services
Authentication: Use client ID + client secret when exchanging codes for tokens
Security model: The secret proves the request is from your legitimate backend, not an attacker
Public Clients
Cannot securely store secrets because the code is distributed to users (browser JavaScript, mobile app binaries).
Types: SPAs, native applications
Authentication: Use client ID only (no secret), but must use PKCE
Security model: PKCE proves the token request comes from the same party that started the authorization flow
Why This Matters
If you try to embed a client secret in a mobile app or SPA, anyone can extract it by examining the app. Once extracted, attackers can impersonate your application. Public clients avoid this by not having secrets to steal.
PKCE (Proof Key for Code Exchange) provides security for public clients by using a one-time cryptographic challenge that proves the token request is legitimate.
OAuth 2.0 / OIDC Configuration
Grant Types
Grant types determine how clients obtain tokens:
Authorization Code: The standard flow for user authentication. Users are redirected to AuthPI, authenticate, and return with a code that’s exchanged for tokens. Use this for web apps, SPAs, and native apps.
Refresh Token: Allows clients to obtain new access tokens without requiring users to re-authenticate. Enable this for better user experience in applications where users stay logged in for extended periods.
Client Credentials: For M2M clients only. The service authenticates directly using its client ID and secret, receiving tokens without user involvement.
Most applications use authorization code + refresh token. M2M services use client credentials.
Response Types
AuthPI supports the code response type (authorization code flow). This is the most secure flow and is recommended by current OAuth 2.0 security best practices. The implicit flow (token response type) is not supported due to its security weaknesses.
Redirect URIs
Redirect URIs are the URLs where AuthPI sends users after authentication. They’re critical for security—AuthPI will only redirect to pre-registered URIs.
For web applications:
https://app.example.com/callback
https://app.example.com/auth/callback
For SPAs:
https://app.example.com/
https://app.example.com/callback
http://localhost:3000/callback (for development)
For native apps:
myapp://callback
com.example.myapp://auth
https://app.example.com/.well-known/callback (claimed HTTPS)
Best practices:
- Use HTTPS in production (HTTP only for localhost development)
- Be specific—don’t use wildcards
- Include development URIs for local testing
- Remove unused URIs to minimize attack surface
You can register up to 10 redirect URIs per client.
Post-Logout Redirect URIs
Similar to redirect URIs, but for where users go after logging out. When your application calls the logout endpoint, AuthPI redirects to one of these URIs.
Scopes
Scopes define what permissions the client can request and what user data it can access.
Standard OIDC scopes:
| Scope | Access Granted |
|---|---|
openid | Required for OIDC. Returns user ID in tokens. |
profile | User’s name, picture, and other profile info |
email | User’s email address and verification status |
phone | User’s phone number and verification status |
address | User’s postal address |
Custom scopes: Your Issuer can define additional scopes for application-specific permissions (e.g., read:projects, admin).
How scopes work:
- You register which scopes the client is allowed to request
- During authorization, the client requests specific scopes
- AuthPI validates the requested scopes against the client’s allowed scopes
- The user may consent to the requested scopes
- Granted scopes appear in the access token
Best practice: Only register scopes the client actually needs. A client that only needs to identify users doesn’t need email or phone scopes.
Organization Restrictions
Clients can also restrict which organization memberships appear in user tokens and /userinfo responses. Configure this under settings.restrictions.organizations.
{
"settings": {
"restrictions": {
"organizations": {
"policy": "allowlist",
"allowed_org_ids": ["org_0gw3hcq8r2kfn7xj9tzm4be5a"]
}
}
}
}
Policies:
all: include all active memberships by default. This is the default when no restriction is configured.none: include no organization claims and reject authorization requests that includeorg.allowlist: include only memberships whose organization ID is inallowed_org_ids.
An authorization request may include org=org_... to issue a token set restricted to one organization. That selected organization must be allowed by the client restriction and the user must be an active member. Refresh tokens keep the same selected-organization marker; they do not carry full organization membership claims.
Token Lifetimes
Configure how long tokens remain valid:
Access token lifetime: How long the access token is valid (default: 30 minutes). Shorter lifetimes are more secure but require more frequent refresh.
ID token lifetime: How long the ID token is valid (default: same as access token). ID tokens are typically validated once at login, so this is less critical.
Refresh token lifetime: How long the refresh token is valid (default: 7 days, capped at 21 days). Longer lifetimes mean users stay logged in longer without re-authenticating — up to the 21-day ceiling, since signing keys rotate and tokens can’t outlive them (see JWKS & key rotation).
Trade-offs:
- Shorter access tokens: More secure (compromised tokens expire quickly) but more refresh traffic
- Longer refresh tokens: Better UX (users stay logged in) but wider window if compromised
- For sensitive applications, use shorter lifetimes and accept the UX trade-off
Session Settings
Clients can override some session behaviors:
Idle timeout: Sessions expire after this period of inactivity. Useful for applications where users should be logged out if they walk away.
Max age: Maximum session lifetime regardless of activity. Forces re-authentication after this period.
Device fingerprint on refresh: Require device verification when refreshing tokens. Adds security but may cause friction for users who switch devices.
Client Secrets
Confidential clients (web apps, M2M) have a client secret used to authenticate with AuthPI.
Secret Handling
At creation: When you create a confidential client, AuthPI generates a secure random secret and returns it once. Store this secret securely—it won’t be shown again.
Storage: AuthPI stores a hash of the secret (using Argon2), not the secret itself. Even if AuthPI’s database were compromised, secrets couldn’t be extracted.
Usage: Include the secret when exchanging authorization codes for tokens or when using client credentials:
POST /token
Authorization: Basic base64(client_id:client_secret)
or in the request body:
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=xxxxx&
client_id=c_xxxxx&
client_secret=your_secret
Secret Rotation
You can rotate client secrets without downtime:
- Request a secret rotation via the API
- AuthPI generates a new secret and returns it
- The old secret remains valid for 15 minutes (grace period)
- Update your application to use the new secret
- After 15 minutes, only the new secret works
This grace period allows you to deploy new credentials without service interruption.
When to rotate:
- Regularly (every 90 days is a common policy)
- If you suspect the secret was exposed
- When team members with access leave
- After security incidents
If You Lose a Secret
Client secrets cannot be recovered—AuthPI only stores hashes. If you lose a secret, rotate it to generate a new one. The old secret stops working after the grace period.
PKCE (Proof Key for Code Exchange)
PKCE adds security to the authorization code flow, especially for public clients that can’t use secrets.
How PKCE Works
- Your app generates a random string (code verifier): 43-128 characters, URL-safe
- Your app creates a challenge from the verifier:
BASE64URL(SHA256(code_verifier)) - Authorization request includes the challenge:
GET /authorize? client_id=c_xxxxx& code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM& code_challenge_method=S256& ... - AuthPI stores the challenge with the authorization code
- Token exchange includes the original verifier:
POST /token code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk& ... - AuthPI validates:
SHA256(code_verifier) == stored_challenge
When PKCE is Required
| Client Type | PKCE Required? |
|---|---|
| Web (confidential) | Recommended but optional |
| SPA (public) | Required |
| Native (public) | Required |
| M2M | Not applicable (no authorization code flow) |
Best practice: Use PKCE for all clients, even confidential ones. It provides defense in depth against authorization code interception.
Client Lifecycle
Creation
Create clients through the Core API or Dashboard. At creation, you specify:
- Name and description
- Application type (web, spa, native, m2m)
- Whether it’s confidential
- Redirect URIs
- Allowed scopes and grant types
For confidential clients, the secret is returned only at creation.
Status Management
Clients have three statuses:
Active: Normal operation. The client can authenticate users and exchange tokens.
Disabled: Temporarily deactivated. The client cannot initiate new authentication flows, but existing tokens may still be valid until they expire. Use this for:
- Temporarily suspending a problematic integration
- Maintenance periods
- Investigating security concerns
Deleted: Marked for removal. The client cannot be used, and after a 31-day grace period, all data is permanently deleted. This grace period allows recovery from accidental deletion.
Updating Clients
You can update most client settings without recreating the client:
- Name and description
- Redirect URIs
- Allowed scopes
- Token lifetimes
- Session settings
Some settings cannot be changed after creation:
- Client ID
- Application type
- Confidentiality (whether secrets are used)
- Issuer
If you need to change these, create a new client.
Deletion
Deleting a client:
- Immediately prevents new authentications
- Existing tokens continue working until they expire (or are explicitly revoked)
- After 31 days, the client is permanently purged
To immediately invalidate all tokens, revoke them before deleting the client.
Internal vs. External Clients
Clients can be marked as internal or external:
Internal clients: Applications you own and operate. These are part of your product and fully under your control.
External clients: Third-party integrations, partner applications, or customer-built integrations. These are applications others build that use your AuthPI-powered authentication.
This distinction is primarily organizational—it helps you track which clients are yours versus which are integrations. Both types work the same technically.
Managing Clients
API Operations
Clients are managed through the Core API:
| Operation | Endpoint |
|---|---|
| Create client | POST /accounts/{account_id}/issuers/{issuer_id}/clients |
| Get client | GET .../clients/{client_id} |
| List clients | GET .../clients |
| Update client | PATCH .../clients/{client_id} |
| Delete client | DELETE .../clients/{client_id} |
| Rotate secret | POST .../clients/{client_id}/rotate-secret |
Dashboard
You can also manage clients through the AuthPI Dashboard, which provides a visual interface for:
- Creating and configuring clients
- Viewing client details
- Rotating secrets
- Monitoring client usage
Security Best Practices
Redirect URI Security
- Always use HTTPS in production
- Match redirect URIs exactly (no wildcards)
- Don’t use open redirectors that could be abused
- Remove old redirect URIs you no longer use
Secret Management
- Never commit secrets to source control
- Use environment variables or secret managers
- Rotate secrets regularly
- Different secrets for different environments (dev, staging, prod)
Token Security
- Use short access token lifetimes for sensitive applications
- Store tokens securely (not in localStorage for SPAs—use memory or HttpOnly cookies)
- Validate tokens on every request
- Implement proper logout (revoke tokens, clear storage)
Public Client Security
- Always use PKCE
- Prefer the system browser over embedded WebViews
- Consider a Backend-for-Frontend pattern for sensitive SPAs
- Be aware of XSS risks—tokens in the browser can be stolen
Example Configurations
E-commerce Web Application
A server-rendered Next.js application with user accounts:
{
"name": "E-commerce Store",
"type": "internal",
"confidential": true,
"settings": {
"protocol": "oidc",
"scopes": ["openid", "profile", "email"],
"openid": {
"application_type": "web",
"grant_types": ["authorization_code", "refresh_token"],
"redirect_uris": [
"https://shop.example.com/api/auth/callback",
"http://localhost:3000/api/auth/callback"
],
"post_logout_redirect_uris": [
"https://shop.example.com/",
"http://localhost:3000/"
],
"default_access_token_age": 3600,
"default_refresh_token_age": 2592000
},
"sessions": {
"idle_timeout": 1800,
"max_age": 604800
}
}
}
Why these settings:
- Confidential because the Next.js backend can store secrets
- Refresh tokens enabled for “stay logged in” functionality
- 30-minute idle timeout so unattended sessions expire
- 7-day max session so users re-authenticate weekly
React Dashboard (SPA)
A React application that calls your API directly:
{
"name": "Analytics Dashboard",
"type": "internal",
"confidential": false,
"settings": {
"protocol": "oidc",
"scopes": ["openid", "profile", "read:analytics"],
"openid": {
"application_type": "spa",
"grant_types": ["authorization_code", "refresh_token"],
"redirect_uris": [
"https://dashboard.example.com/",
"https://dashboard.example.com/callback",
"http://localhost:5173/callback"
],
"default_access_token_age": 900,
"default_refresh_token_age": 86400
}
}
}
Why these settings:
- Public client (no secret) because code runs in browser
- Must use PKCE during authorization
- Short access token (15 min) since SPAs are more exposed
- 24-hour refresh token as a balance between UX and security
- Custom scope
read:analyticsfor API access
iOS Mobile App
A native iOS application:
{
"name": "Mobile App",
"type": "internal",
"confidential": false,
"settings": {
"protocol": "oidc",
"scopes": ["openid", "profile", "email", "offline_access"],
"openid": {
"application_type": "native",
"grant_types": ["authorization_code", "refresh_token"],
"redirect_uris": [
"com.example.myapp://callback",
"https://example.com/.well-known/apple-app-site-association/callback"
],
"default_access_token_age": 3600,
"default_refresh_token_age": 7776000
}
}
}
Why these settings:
- Public client (no secret) because app binary is distributed
- Custom URL scheme for in-app redirect
- Universal link as alternative for iOS
- Long refresh token (90 days) so users rarely re-authenticate on mobile
- Must use PKCE during authorization
Data Processing Service (M2M)
A backend service that runs nightly data exports:
{
"name": "Data Export Service",
"type": "internal",
"confidential": true,
"settings": {
"protocol": "oidc",
"scopes": ["read:users", "read:analytics"],
"openid": {
"application_type": "m2m",
"grant_types": ["client_credentials"],
"default_access_token_age": 3600
}
}
}
Why these settings:
- Confidential because it runs on your servers
- Client credentials grant (no user involvement)
- No redirect URIs (no browser flow)
- Scopes for specific API access needed by the service
- No refresh token (service re-authenticates as needed)
Third-Party Integration
A partner’s application that integrates with your platform:
{
"name": "Partner Integration - Acme Corp",
"type": "external",
"confidential": true,
"settings": {
"protocol": "oidc",
"scopes": ["openid", "profile", "read:projects"],
"openid": {
"application_type": "web",
"grant_types": ["authorization_code"],
"redirect_uris": [
"https://acme.example.com/integrations/yourapp/callback"
],
"default_access_token_age": 3600,
"default_refresh_token_age": 86400
}
}
}
Why these settings:
- External type to track it’s a third-party integration
- Limited scopes (only what the integration needs)
- Single redirect URI (their specific callback)
- Shorter refresh token than internal apps
- No refresh token could be even more conservative
Troubleshooting
”invalid_client” Error
The client wasn’t found or couldn’t be authenticated.
Check:
- Client ID is correct
- Client status is “active” (not disabled or deleted)
- For confidential clients, the secret is correct
- The client belongs to the issuer you’re calling
”invalid_redirect_uri” Error
The redirect URI in your request doesn’t match any registered URI.
Check:
- URI matches exactly (including trailing slashes, query parameters)
- URI is registered in the client’s redirect_uris
- Protocol matches (http vs https)
“invalid_grant” Error
The authorization code or refresh token couldn’t be validated.
Check:
- Code hasn’t expired (codes are short-lived)
- Code hasn’t already been used (single-use)
- PKCE verifier matches the challenge (for public clients)
- Refresh token hasn’t been revoked or rotated
”unauthorized_client” Error
The client isn’t allowed to use the requested grant type.
Check:
- Grant type is in the client’s
grant_typesconfiguration - You’re not using client_credentials for a public client
- You’re not using authorization_code for an M2M client
Next Steps
- Set up authentication with the Quick Start guide
- Learn about Users who authenticate through clients
- Understand Issuers that clients connect to
- Explore the TypeScript SDK for easier integration