Skip to content

A client represents an application or service identity for OAuth and API access. Clients are how applications authenticate with Hantera without requiring human interaction.

TIP

This page covers clients in depth. For an overview of IAM, see the IAM Overview.

What is a Client?

Clients represent applications or services that need to access Hantera APIs - whether they're mobile apps, web applications, backend services, or third-party integrations.

Key characteristics:

  • OAuth 2.0 configuration (redirect URIs, grant types, scopes)
  • Client secret authentication
  • No password-based login (cannot log in to portal)
  • Can be assigned roles for API access
  • System clients are protected from modification

Example client:

json
{
  "id": "01933e8f-7c45-7123-9abc-123456789xyz",
  "type": "client",
  "properties": {
    "name": "Mobile App",
    "description": "iOS and Android mobile application",
    "redirectUris": ["myapp://callback", "myapp://logout"],
    "grantTypes": ["authorization_code", "refresh_token"],
    "scopes": ["openid", "profile", "email"],
    "tokenEndpointAuthMethod": "client_secret_post"
  },
  "roles": ["app:mobile:access"],
  "suspendedAt": null
}

When to Use Clients

✅ Use clients for:

  • Third-party integrations
  • Mobile or web applications (OAuth flows)
  • Service-to-service communication
  • Webhook consumers
  • Background jobs requiring API access
  • Developer tools and scripts

❌ Don't use clients for:

  • Human users needing portal access (use Principals instead)
  • Accounts that need password login (use Principals instead)

Client Types

Interactive Clients

Interactive clients use OAuth flows where a user grants permission to the client.

Characteristics:

  • Require redirect URIs for OAuth callbacks
  • Support authorization code flow
  • User consent required
  • Best for mobile apps, SPAs, web applications

Example use cases:

  • Mobile application that accesses user's Hantera data
  • Third-party app integration
  • Partner portal access

Grant types:

  • authorization_code - Standard OAuth flow
  • refresh_token - Long-lived access

Example configuration:

json
{
  "properties": {
    "name": "Partner Mobile App",
    "redirectUris": ["myapp://callback"],
    "grantTypes": ["authorization_code", "refresh_token"],
    "scopes": ["openid", "profile", "orders:read"]
  }
}

Service Accounts

Service accounts are non-interactive clients for server-to-server communication.

Characteristics:

  • No redirect URIs needed
  • Use client credentials flow
  • No user interaction required
  • Best for background services, APIs, batch jobs

Example use cases:

  • Integration service syncing data
  • Scheduled batch processing
  • Webhook receiver
  • Internal microservice

Grant types:

  • client_credentials - Server-to-server flow

Example configuration:

json
{
  "properties": {
    "name": "Integration Service",
    "description": "Data synchronization service",
    "grantTypes": ["client_credentials"]
  },
  "roles": ["integration:sync:access"]
}

System Clients

System clients are built-in clients managed by Hantera.

Characteristics:

  • Cannot be modified or deleted
  • Reserved for Hantera's internal use
  • Predefined configuration

INFO

System clients are read-only. They cannot be created, modified, or deleted via the API.

OAuth Configuration

Grant Types

Clients support different OAuth 2.0 grant types:

authorization_code

Standard OAuth authorization flow for user-facing applications.

Flow:

  1. User redirects to authorization endpoint
  2. User grants permission
  3. Client receives authorization code
  4. Client exchanges code for access token

Use for:

  • Web applications
  • Mobile applications
  • Single-page applications (SPA)

refresh_token

Allows obtaining new access tokens without user interaction.

Flow:

  1. Client uses refresh token
  2. Receives new access token

Use for:

  • Long-lived sessions
  • Background sync
  • Always combine with authorization_code

client_credentials

Server-to-server authentication without user context.

Flow:

  1. Client authenticates with client ID and secret
  2. Receives access token

Use for:

  • Service accounts
  • Background jobs
  • API integrations
  • Webhook handlers

Redirect URIs

Redirect URIs specify where users are sent after OAuth authorization.

Requirements:

  • Must be HTTPS in production (HTTP allowed for localhost)
  • Custom schemes allowed for mobile apps (e.g., myapp://callback)
  • Exact match required (no wildcards)
  • Multiple URIs supported

Example:

json
{
  "redirectUris": [
    "https://app.example.com/oauth/callback",
    "https://app.example.com/oauth/logout",
    "myapp://callback",
    "http://localhost:3000/callback"
  ]
}

Scopes

Scopes define what permissions the client requests.

Standard OpenID Connect scopes:

  • openid - Basic identity information
  • profile - User profile data
  • email - User email address

Custom Hantera scopes:

  • Resource-based scopes (e.g., orders:read, orders:write)
  • Application-specific scopes

Example:

json
{
  "scopes": [
    "openid",
    "profile",
    "orders:read",
    "customers:read"
  ]
}

Token Endpoint Authentication

Clients authenticate at the token endpoint using different methods:

client_secret_post

Client sends ID and secret in POST body.

http
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=ABC123&
client_id=client_id_here&
client_secret=secret_here

client_secret_basic

Client sends ID and secret in Authorization header.

http
POST /oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=ABC123

Client Secret Management

Creating Secrets

Generate a new client secret:

http
POST /resources/iam/clients/{id}/secrets
If-Match: "etag-value"
{
  "description": "Production API access",
  "expiresAt": "2026-01-01T00:00:00Z"
}

Response:

json
{
  "id": "01933e8f-7c45-7123-secret-abc123",
  "secret": "sk_live_abcdef123456...",
  "description": "Production API access",
  "createdAt": "2025-01-07T15:30:00Z",
  "expiresAt": "2026-01-01T00:00:00Z",
  "lastUsedAt": null
}

WARNING

The client secret is only shown once during creation. Store it securely - it cannot be retrieved later.

Secret Best Practices

Expiration:

  • Always set expiration dates for secrets
  • Rotate secrets before expiration
  • Use short expiration for high-security environments

Description:

  • Document secret purpose
  • Include environment (production, staging)
  • Note the system using the secret

Security:

  • Never commit secrets to version control
  • Use environment variables or secret managers
  • Rotate compromised secrets immediately
  • Monitor lastUsedAt for unused secrets

Listing Secrets

View all active secrets for a client:

http
GET /resources/iam/clients/{id}/secrets

Response:

json
[
  {
    "id": "01933e8f-7c45-7123-secret-abc123",
    "description": "Production API access",
    "createdAt": "2025-01-07T15:30:00Z",
    "expiresAt": "2026-01-01T00:00:00Z",
    "lastUsedAt": "2025-01-08T10:00:00Z"
  }
]

INFO

The actual secret value is never returned after creation. Only metadata is shown.

Revoking Secrets

Delete a client secret:

http
DELETE /resources/iam/clients/{id}/secrets/{secretId}

Returns 204 No Content on success. The secret is immediately invalidated.

When to revoke:

  • Secret compromised or exposed
  • Secret no longer needed
  • Before secret expiration (graceful rotation)
  • During security incidents

List API Features

The clients list endpoint supports filtering, search, and cursor-based pagination.

Cursor-Based Pagination

All list operations use cursor-based pagination:

Query parameters:

  • limit - Items per page (default: 50, max: 100)
  • after - Cursor for next page
  • before - Cursor for previous page

Response format:

json
{
  "clients": [...],
  "totalCount": 42,
  "lastCursor": "01933e8f-7c45-7123-9abc-123456789abc",
  "firstCursor": "01933e8f-7c45-7123-9abc-123456789def"
}

Example:

http
GET /resources/iam/clients?limit=50
GET /resources/iam/clients?limit=50&after=cursor_here

Search by client name (case-insensitive, partial matches):

http
GET /resources/iam/clients?search=mobile

Filtering

Filter by type:

http
GET /resources/iam/clients?type=service_account

Include suspended clients:

http
# By default, suspended clients are excluded
GET /resources/iam/clients?includeSuspended=true

Sorting

Sort results using the orderBy parameter:

http
# Sort by name ascending (default)
GET /resources/iam/clients?orderBy=name asc

# Sort by created date
GET /resources/iam/clients?orderBy=createdAt desc

Client Management

Create or Update Client

Full replacement of a client:

http
PUT /resources/iam/clients/{id}
If-Match: "etag-value"
{
  "properties": {
    "name": "Mobile App",
    "description": "iOS and Android application",
    "redirectUris": ["myapp://callback"],
    "grantTypes": ["authorization_code", "refresh_token"],
    "scopes": ["openid", "profile", "orders:read"],
    "tokenEndpointAuthMethod": "client_secret_post"
  },
  "roles": ["app:mobile:access"]
}

WARNING

The If-Match header with ETag is required for updates to prevent conflicting changes. Omit it only when creating a new client.

Partial Update

Update specific properties:

http
PATCH /resources/iam/clients/{id}
If-Match: "etag-value"
{
  "name": "Mobile App v2",
  "description": "Updated mobile application"
}

Update Roles

Replace the entire roles list:

http
PUT /resources/iam/clients/{id}/roles
If-Match: "etag-value"
{
  "roles": ["app:mobile:access", "integration:api:read"]
}

Get Client Details

Retrieve a client with all properties:

http
GET /resources/iam/clients/{id}

Response:

json
{
  "id": "01933e8f-7c45-7123-9abc-123456789xyz",
  "name": "Mobile App",
  "description": "iOS and Android application",
  "redirectUris": ["myapp://callback"],
  "grantTypes": ["authorization_code", "refresh_token"],
  "scopes": ["openid", "profile", "orders:read"],
  "tokenEndpointAuthMethod": "client_secret_post",
  "roles": ["app:mobile:access"],
  "suspendedAt": null,
  "createdAt": "2024-01-01T10:00:00Z",
  "etag": "W/\"abc123\""
}

Delete Client

Remove a client permanently:

http
DELETE /resources/iam/clients/{id}

Returns 204 No Content on success.

WARNING

Deletion is permanent and cannot be undone. All associated secrets are also deleted.

Integration Patterns

OAuth Authorization Code Flow

  1. Create OAuth client

    http
    PUT /resources/iam/clients/mobile-app-v1
    {
      "properties": {
        "name": "Mobile App",
        "redirectUris": ["myapp://callback"],
        "grantTypes": ["authorization_code", "refresh_token"]
      }
    }
  2. Generate client secret

    http
    POST /resources/iam/clients/mobile-app-v1/secrets
    {
      "description": "Production secret"
    }
  3. Redirect user to authorization endpoint

    GET /oauth/authorize?
      response_type=code&
      client_id=mobile-app-v1&
      redirect_uri=myapp://callback&
      scope=openid%20profile%20orders:read
  4. Exchange authorization code for tokens

    http
    POST /oauth/token
    {
      "grant_type": "authorization_code",
      "code": "auth_code_here",
      "client_id": "mobile-app-v1",
      "client_secret": "secret_here",
      "redirect_uri": "myapp://callback"
    }
  5. Use access token for API calls

    http
    GET /resources/orders/123
    Authorization: Bearer access_token_here

Service Account Pattern

  1. Create service account client

    http
    PUT /resources/iam/clients/sync-service
    {
      "properties": {
        "name": "Data Sync Service",
        "grantTypes": ["client_credentials"]
      },
      "roles": ["integration:sync:full"]
    }
  2. Generate long-lived secret

    http
    POST /resources/iam/clients/sync-service/secrets
    {
      "description": "Production sync service",
      "expiresAt": "2026-12-31T23:59:59Z"
    }
  3. Obtain access token

    http
    POST /oauth/token
    {
      "grant_type": "client_credentials",
      "client_id": "sync-service",
      "client_secret": "secret_here"
    }
  4. Make API calls

    http
    GET /resources/orders
    Authorization: Bearer access_token_here

Secret Rotation

  1. Create new secret (before old expires)

    http
    POST /resources/iam/clients/{id}/secrets
    {
      "description": "Rotated secret 2025-01",
      "expiresAt": "2026-01-31T00:00:00Z"
    }
  2. Deploy new secret to application Update environment variables or secret manager

  3. Verify new secret works Test authentication with new secret

  4. Revoke old secret

    http
    DELETE /resources/iam/clients/{id}/secrets/{old-secret-id}

API Endpoints

Client management endpoints:

  • GET /resources/iam/clients - List clients
  • GET /resources/iam/clients/{id} - Get client
  • PUT /resources/iam/clients/{id} - Create or update (full)
  • PATCH /resources/iam/clients/{id} - Update properties (partial)
  • DELETE /resources/iam/clients/{id} - Delete client
  • PUT /resources/iam/clients/{id}/roles - Update roles
  • GET /resources/iam/clients/{id}/secrets - List secrets
  • POST /resources/iam/clients/{id}/secrets - Create secret
  • DELETE /resources/iam/clients/{id}/secrets/{secretId} - Revoke secret

For complete endpoint documentation, request/response formats, and error codes, see the HTTP API Reference.

Security Considerations

Secret Storage

Never:

  • ❌ Commit secrets to version control
  • ❌ Log secrets in plain text
  • ❌ Include secrets in URLs
  • ❌ Send secrets via email
  • ❌ Store secrets in client-side code

Always:

  • ✅ Use environment variables
  • ✅ Use secret management services (AWS Secrets Manager, Azure Key Vault)
  • ✅ Rotate secrets regularly
  • ✅ Set expiration dates
  • ✅ Monitor secret usage

Rate Limiting

Implement rate limiting for client authentication:

  • Prevent brute force attacks
  • Monitor failed authentication attempts
  • Suspend clients with excessive failures

Audit Logging

Track client activity:

  • Authentication attempts
  • API calls made
  • Secret creation/revocation
  • Configuration changes

Troubleshooting

Invalid Client Error

Cause: Client ID doesn't exist or client is suspended

Solution:

  • Verify client ID is correct
  • Check if client exists: GET /resources/iam/clients/{id}
  • Check if client is suspended (look for suspendedAt)

Invalid Client Secret

Cause: Secret is incorrect, expired, or revoked

Solution:

  • Verify secret is correct
  • Check secret expiration date
  • Generate new secret if needed
  • Ensure secret hasn't been revoked

Invalid Redirect URI

Cause: Redirect URI doesn't match client configuration

Solution:

  • Verify exact URI match (including protocol, port, path)
  • Check client configuration: GET /resources/iam/clients/{id}
  • Update client if redirect URI changed

Unsupported Grant Type

Cause: Requested grant type not configured for client

Solution:

  • Check client's grantTypes configuration
  • Update client to include required grant type

© 2024 Hantera AB. All rights reserved.