Skip to main content
This guide walks through implementing authentication in your Cloud App. By the end, your backend will be able to request access tokens from JTL, verify session tokens from the AppBridge, and make authenticated API calls on behalf of a merchant’s tenant. If you need a conceptual overview of how JTL authentication works across Cloud, OnPremise, and SCX, see the OAuth 2.0 Flow and API Keys & Tokens pages.

Authentication Tokens and Their Roles

Cloud Apps use two tokens that work together: an access token and a session token. The access token authorizes your app to call JTL’s tenant-specific APIs. The session token identifies which merchant the request is for. Most JTL Cloud API requests need both: the access token goes in the Authorization header, and the tenant ID (read from the verified session token) goes in the X-Tenant-ID header.

How They Fit Together

  1. Your frontend asks AppBridge for a session token.
  2. Your frontend sends the session token to your backend.
  3. Your backend verifies the session token and reads the tenantId from its payload.
  4. Your backend separately fetches an access token using its client credentials.
  5. Your backend calls the JTL Cloud API with the access token in Authorization: Bearer and the tenant ID in X-Tenant-ID.

Client Credentials: Getting an Access Token

Your backend authenticates with JTL’s Identity Provider using the CLIENT_ID and CLIENT_SECRET you received when registering your app in the Partner Portal.

Implementation

What this does: Encodes your client credentials as Base64, sends them to JTL’s auth endpoint with the client_credentials grant type, and returns a JWT access token. This token authenticates your backend for API calls.

Token Response

A successful request returns:

Caching and Refreshing Tokens

Access tokens are valid for approximately 24 hours. Requesting a new token on every API call adds latency and unnecessary load on the auth server. Cache the token and refresh it before it expires.
What this does: Stores the access token in memory and reuses it until 60 seconds before expiry. When the buffer is reached, it fetches a fresh token. This prevents both unnecessary auth requests and failures from expired tokens mid-request.
This in-memory cache works for single-instance servers. If you’re running multiple instances (e.g., behind a load balancer), use a shared cache like Redis instead.

Session Tokens: Verifying the Frontend User

When your app runs inside the App Shell, the frontend gets a session token from the AppBridge. This token identifies who the user is and which tenant (merchant) they belong to. Your backend must verify this token before trusting it.

How it Works

  1. Your frontend calls appBridge.method.call('getSessionToken') to get a session token from the App Shell
  2. The frontend sends this token to your backend (through the header)
  3. Your backend fetches JTL’s public keys (JWKS) and uses them to verify the token’s signature
  4. The verified payload contains the tenantId, userId, and tenantSlug

Session Token Payload

A decoded session token contains:

Implementation

What this does: Fetches JTL’s public keys from the JWKS endpoint (authenticated with your access token) and verifies the token’s signature. The returned payload tells you which user and tenant the request belongs to.
In production, cache the JWKS response. Public keys change infrequently, so fetching them on every request adds unnecessary latency.

Wiring it Together: The connect-tenant Route

The connect-tenant pattern ties both flows together. Your frontend gets a session token, sends it as a header (X-Session-ID) to your backend, and your backend verifies it and returns the tenant details.
What this does: Receives the session token from your frontend, verifies it using JWKS, and returns the tenant details. In a production app, this is where you would store the tenant connection in your database so you can associate future API calls with the correct merchant.

Calling from the Frontend

Your frontend sends the session token to this route after the AppBridge initializes:
What this does: Gets the session token from the App Shell via AppBridge, sends it to your backend for verification, and receives the verified tenant ID. Your frontend can then pass this tenant ID in subsequent API requests. For the full frontend integration pattern using React Context, see the AppBridge Provider in the From Scratch quickstart.

Tenant Mapping

When a merchant installs your app, you need to store a record linking their JTL tenant ID to your app’s internal state. Without this mapping, your backend has no way to associate future requests. In-memory storage works in development but is wiped on every restart and does not survive multiple server instances. Use a persistent store from the start.

What to Store

At minimum, persist the following on install: If your app has its own user or account model, link the tenantId to your internal record.

When to Write

Write the record in your /api/connect-tenant handler, after you verify the session token and before you return success to the frontend. Use an upsert rather than an insert: the same merchant may reinstall your app, and a duplicate-key error on reinstall is a poor experience. A minimal PostgreSQL schema:
Then upsert on install:
What this does: Verifies the session token to get a trusted tenant ID, then writes (or updates) the mapping in your database. The ON CONFLICT clause handles reinstalls cleanly.

When to Read

On every incoming request from your frontend, extract the tenant ID from the verified session token and look up your internal record:
A merchant who uninstalls and reinstalls should invalidate any cached state your app holds for that tenant.

What Not to Do

A few anti-patterns cause most tenant-mapping bugs in production. Avoid each of these from the start.

Token Lifecycle

Understanding when tokens expire and how to handle expiry prevents intermittent auth failures in production.

Access Tokens

Access tokens from the client credentials flow expire after approximately 24 hours (86399 seconds). Your backend should cache and reuse the token, refreshing it before expiry. See the token caching example above. If an API call returns 401 Unauthorized, clear your cached token and request a new one before retrying:
What this does: Attempts the API call with the cached token. If the server returns 401, it clears the cache, gets a fresh token, and retries once. This handles the edge case where a token expires between the cache check and the API call.

Session Tokens

Session tokens from the AppBridge are short-lived. If your frontend holds a session token too long, verification will fail on the backend. Request a fresh session token before each backend call, or at minimum before operations that require verified identity:

Common Authentication Errors

These are the most frequent authentication issues and how to resolve them.
Your CLIENT_ID or CLIENT_SECRET is incorrect. Verify both values in your .env file. Check for extra whitespace, missing characters, or swapped values. If you’ve lost your secret, regenerate credentials by creating a new app in the Partner Portal.
Your access token has expired. If you’re caching tokens, make sure you refresh before the expires_in window closes. The token caching example refreshes 60 seconds before expiry to prevent this.
The session token from AppBridge could not be verified.Common causes: the JWKS endpoint returned an error (check your access token), or the session token has expired (request a fresh one from AppBridge).
The JWKS endpoint requires a valid access token in the Authorization header. Make sure you’re passing Bearer <access_token>, not the session token or client credentials. If the access token itself is expired, refresh it first.

What’s Next

Using Platform APIs

Call the JTL Cloud and JTL-Wawi REST and GraphQL APIs with your authenticated tokens.

App Shell & UI Integration

Reference for the manifest, AppBridge API, and Platform UI components.

Best Practices

Production patterns for token caching, error handling, and security.