<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=749646578535459&amp;ev=PageView&amp;noscript=1">
Skip to content
  • There are no suggestions because the search field is empty.

Authenticating with OAuth 2.0

Authenticate with the KaiNexus API using OAuth 2.0 Client Credentials

This guide explains how to authenticate external applications with the KaiNexus API using OAuth 2.0 Client Credentials. This is the recommended authentication method for secure machine-to-machine integrations.

OAuth 2.0 allows your application to authenticate without user interaction while using short-lived access tokens that align with modern security standards and common compliance requirements.

KaiNexus acts as both the:

  • Authorization Server (issues tokens)
  • Resource Server (validates tokens)

No external identity provider is required.

OAuth server metadata is published at: https://api.kainexus.com/.well-known/oauth-authorization-server

Prerequisites

Before you begin, you must have a Service Account configured in KaiNexus with an OAuth 2.0 Credential (Client ID and Client Secret).

Service Accounts can include multiple credentials and support both API Key and OAuth 2.0 authentication methods.

Ofie Profile PicPro Tip: If you haven't set up Service Accounts yet, reference our Managing Service Accounts support page to learn how. 

Quick Reference

Use the following OAuth 2.0 specifications when configuring your integration with the KaiNexus API:

Requirement Value

Grant Type

client_credentials
Authentication Method client_secret_basic (HTTP Basic Auth)
Scope None (Leave this field blank)
Token Lifetime 3600 seconds (1 hour)
Token Type JWT bearer token (Signed via RS256)
Refresh Tokens Not Supported (Request a new token upon expiry)
Custom Claims knx_org_user_id (Identifies the Service Account)

How Authentication Works

Ofie Profile PicImportant: KaiNexus does not use OAuth scopes. Access is controlled entirely through the Service Account’s assigned Network Locations and Roles.

KaiNexus supports the OAuth 2.0 Client Credentials grant type only. This flow is for machine-to-machine integrations and requires no interactive user login.

Unlike a static API Key, OAuth 2.0 uses short-lived access tokens obtained through a two-step authentication process:

  1. Request a Token: Your application sends the Client ID and Client Secret to the KaiNexus token endpoint.

  2. Receive an access token: KaiNexus validates the credentials and returns a temporary access token valid for 1 hour.

  3. Call the API: Your application includes the access token in the Authorization header of API requests.

Requesting an Access Token

Using Postman

Go to the Authorization tab and select OAuth 2.0. When configuring a new token:

  • Set Grant Type to Client Credentials.

  • Set Access Token URL to https://api.kainexus.com/oauth/token.

  • Enter your Client ID and Client Secret.

  • Set Client Authentication to "Send as Basic Auth header".

  • Ensure the Scope field is empty. 

  • Request the token. 

Using Code (cURL Example)

Send a POST request to the /oauth/token endpoint with the client_credentials grant type and client_secret _basic or client_secret_post authentication. 

Example: 

curl --request POST \

  --url "https://api.kainexus.com/oauth/token" \

  --user "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \

  --header "Content-Type: application/x-www-form-urlencoded" \

  --data "grant_type=client_credentials"

Successful response: 

{

  "access_token": "eyJraWQiOiJ...<jwt>...",

  "token_type": "Bearer",

  "expires_in": 3600

}

Common Errors: 

{"error": "invalid_client", "error_description": "Client authentication failed."}

{"error": "invalid_scope", "error_description": "Scopes are not supported."}

Other standard OAuth errors (unsupported_grant_type, invalid_request, server_error) follow RFC 6749.

Using the Bearer Token

Include the access token in the Authorization header of all API requests using the Bearer scheme.

ACCESS_TOKEN="eyJraWQiOiJ...<jwt>..."




curl --request GET \

  --url "https://api.kainexus.com/api/..." \

  --header "Authorization: Bearer ${ACCESS_TOKEN}"

Important Rules

  • Never send a scope parameter. Sending scope results in an invalid_scope error.
  • Do not include client_id or client_secret in the request URL. 
  • KaiNexus API endpoints (/api/public/v1/**) accept both legacy HTTP Basic and OAuth bearer tokens.
  • Use the correct base URL for your environment:

    • Production (default): api.kainexus.com
    • EU environment: eu-prod.kainexus.com
    • Sandbox: sandbox.kainexus.com

Token Lifecycle and Security

KaiNexus issues JWT access tokens signed with RS256. Token lifetime is 3600 seconds (1 hour).

KaiNexus does not use refresh tokens. When an access token expires, request a new one using the same Client ID and Client Secret.

Caching and Rate Limits

The /oauth/token endpoint is rate-limited per client IP:

  • 5 requests per 10 seconds
  • 20 requests per minute
  • 100 requests per hour

Because of these limits, integrations should cache and reuse access tokens until they are close to expiration. Requesting a new token for every API call will trigger rate limiting and cause authentication failures.

Recommended token handling

  • Store the client_ID and client_secret securely, such as in a secrets manager or encrypted configuration store
  • Cache access tokens in memory or another secure location
  • Request a new token shortly before expiration, such as when expires_in is within 60 seconds of expiring. 
  • Do not request a new token for every API request

Token Validation

To validate tokens externally:

  1. Retrieve the JWKS from the OAuth discovery endpoint.
  2. Verify the JWT signature using the published public key.
  3. Validate standard claims, including: iss, aud, sub, client_id, exp, iat, jti.

KaiNexus access tokens also include a custom claim, knx_org_user_id, which identifies the associated Service Account.

HTTP and OAuth Libraries

Any standard HTTP client can be used to request and send OAuth tokens. Common examples include:

  • requests (Python)
  • axios (Node.js)
  • HttpClient (.net)
  • net/http (Go)

Note: OAuth libraries can be used, but ensure they do not automatically include scope parameters, as KaiNexus does not support OAuth scopes.

Examples include: Authlib (Python), openid-client (Node.js), golang.org/x/oauth2 (Go), Spring Security OAuth2 Client (Java), and Duende IdentityModel (.NET).

Code Examples

The following examples demonstrate token caching and reuse. Production implementations should add error handling for rate limits and network failures.

Replace `/api/...` with the actual KaiNexus endpoint your integration needs to call, for example, a `/api/public/v1/...` endpoint.

Bash

#!/usr/bin/env bash
set -euo pipefail

# Configuration
BASE_URL="${KAINEXUS_BASE_URL:?Set KAINEXUS_BASE_URL}"
CLIENT_ID="${KAINEXUS_CLIENT_ID:?Set KAINEXUS_CLIENT_ID}"
CLIENT_SECRET="${KAINEXUS_CLIENT_SECRET:?Set KAINEXUS_CLIENT_SECRET}"

ACCESS_TOKEN=""
EXPIRES_AT=0

# Function to handle OAuth2 Token retrieval and caching
get_access_token() {
  local now
  now=$(date +%s)

  # Use cached token if it's still valid
  if [[ -n "${ACCESS_TOKEN}" && "${now}" -lt "${EXPIRES_AT}" ]]; then
    printf '%s\n' "${ACCESS_TOKEN}"
    return
  fi

  local response expires_in
  response=$(
    curl --silent --show-error --fail \
      --request POST \
      --url "${BASE_URL}/oauth/token" \
      --user "${CLIENT_ID}:${CLIENT_SECRET}" \
      --header "Content-Type: application/x-www-form-urlencoded" \
      --data "grant_type=client_credentials"
  )

  ACCESS_TOKEN=$(jq -r '.access_token' <<<"${response}")
  expires_in=$(jq -r '.expires_in' <<<"${response}")

  if [[ -z "${ACCESS_TOKEN}" || "${ACCESS_TOKEN}" == "null" ]]; then
    echo "Token response did not include an access_token" >&2
    return 1
  fi

  # Set expiry with a 60-second safety buffer
  EXPIRES_AT=$(( now + expires_in - 60 ))
  printf '%s\n' "${ACCESS_TOKEN}"
}

# Function to make an authenticated API call
call_api() {
  local token
  token=$(get_access_token)

  curl --silent --show-error --fail \
    --request GET \
    --url "${BASE_URL}/api/..." \
    --header "Authorization: Bearer ${token}"
}

# Execute the call
call_api

Python

import os
import time
import requests

class KaiNexusApiClient:
    def __init__(self, base_url: str, client_id: str, client_secret: str) -> None:
        self.base_url = base_url.rstrip("/")
        self.client_id = client_id
        self.client_secret = client_secret
        self._access_token = None
        self._expires_at = 0

    def get_access_token(self) -> str:
        now = time.time()
        # Check if we have a valid cached token
        if self._access_token and now < self._expires_at:
            return self._access_token

        # Request a new token
        response = requests.post(
            f"{self.base_url}/oauth/token",
            auth=(self.client_id, self.client_secret),
            data={"grant_type": "client_credentials"},
            timeout=30,
        )
        response.raise_for_status()

        payload = response.json()
        self._access_token = payload["access_token"]
        expires_in = int(payload["expires_in"])
        
        # Set expiry time with a 60-second safety buffer
        self._expires_at = now + expires_in - 60
        return self._access_token

    def get(self, path: str, **kwargs) -> requests.Response:
        headers = dict(kwargs.pop("headers", {}))
        # Automatically inject the Bearer token into headers
        headers["Authorization"] = f"Bearer {self.get_access_token()}"

        response = requests.get(
            f"{self.base_url}{path}",
            headers=headers,
            timeout=30,
            **kwargs,
        )
        response.raise_for_status()
        return response

if __name__ == "__main__":
    # Initialize the client using environment variables
    client = KaiNexusApiClient(
        base_url=os.environ["KAINEXUS_BASE_URL"],
        client_id=os.environ["KAINEXUS_CLIENT_ID"],
        client_secret=os.environ["KAINEXUS_CLIENT_SECRET"],
    )

    # Example API call
    response = client.get("/api/...")
    print(response.json())