Build the Slim 4 backend for a JTL Cloud App using PHP 8 and the built-inDocumentation Index
Fetch the complete documentation index at: https://developer.jtl-software.com/llms.txt
Use this file to discover all available pages before exploring further.
sodium_crypto_sign_verify_detached function for Ed25519 signature verification. By the end, the backend runs locally and the frontend’s “Waiting for backend” placeholder turns into a real connection.
Stack: PHP 8.1+, Slim 4, vlucas/phpdotenv for environment variables, native ext-sodium for JWT verification.
Prerequisites
You need:- ✅ A finished frontend from the Build the Frontend page, running locally on
http://localhost:5173 - ✅ PHP 8.1 or higher with
ext-sodiumenabled. Runphp --versionandphp -m | grep sodiumto check. - ✅ Composer for dependency management. Run
composer --versionto check.
What you’re Building
During setup, your backend verifies that the session token from your frontend is valid and was issued by JTL. To do this:- Your backend authenticates with JTL using its client credentials
- Fetches JTL’s public keys (JWKS)
- And uses them to verify the session token’s signature
1. Set up the Project
Create abackend folder alongside the existing frontend folder, then initialise a Composer project inside it.
2. Install Packages
| Package | Purpose |
|---|---|
slim/slim | The Slim 4 micro-framework: routing, request and response handling |
slim/psr7 | The PSR-7 implementation Slim uses by default |
vlucas/phpdotenv | Loads environment variables from a .env file |
3. Set up Environment Variables
Createbackend/.env:
/api/* requests to localhost:5273, so the PORT value matches that target.
Add .env and vendor/ to backend/.gitignore so secrets and dependencies don’t end up in version control:
4. Build the Auth Helper
The first piece of the backend is a function that authenticates with JTL using your client credentials and returns an access token. This token has two uses: fetching the public keys for session token verification, and making tenant-scoped calls to the JTL Cloud API. Createbackend/src/JtlAuth.php:
client_credentials grant request to JTL’s auth endpoint, and returns the resulting access token. The token is short-lived, so the function fetches a fresh one on each call. For higher-traffic backends you’d add caching.
5. Build the Session Verifier
With an access token in hand, the backend can fetch the public keys it needs to verify session tokens. Thesodium_crypto_sign_verify_detached function verifies Ed25519 signatures directly against the public key.
Create backend/src/SessionVerifier.php:
sodium_crypto_sign_verify_detached. If the signature is valid and the token isn’t expired, the method returns the decoded payload as an associative array.
In production, select the correct key by matching the
kid in the session token’s header against the keys in the JWKS response. This example uses the first key for simplicity, which works as long as the JWKS only contains one key.6. Build the Connect Tenant Endpoint
Now connect the verifier into a Slim route. The frontend’s shell layout sends the session token to/api/connect-tenant via the X-Session-ID header.
Create backend/public/index.php:
.env, instantiates JtlAuth with the credentials, and passes that into SessionVerifier. Both instances are then captured by the route closure via use ($verifier) and shared across requests.
The CORS middleware allows requests from the Vite dev server on port 5173. The dev proxy in vite.config.ts already routes frontend fetch('/api/...') calls to this backend, but the CORS headers are a useful safety net during development.
See the Tenant Mapping section for more on managing tenants in production.
7. Configure Composer Autoloading
Openbackend/composer.json and add a psr-4 autoload mapping for the App\ namespace:
App\ namespace lives under the src/ directory, which is how JtlAuth and SessionVerifier get loaded when index.php references them.
8. Run the Backend
Start the dev server:401 response with {"error":"Failed to verify session token"}. That’s the expected outcome for an invalid token. A real session token from the App Shell will follow the same path and succeed.
Common Issues
'Class App\JtlAuth not found' or similar autoload error
'Class App\JtlAuth not found' or similar autoload error
This error means Composer’s autoloader doesn’t know where to find the class. The most common cause is forgetting to regenerate the autoload files after adding the
psr-4 mapping to composer.json.Running composer dump-autoload from the backend/ directory rebuilds vendor/autoload.php to include any new namespace mappings. The error should clear after the next request.'CLIENT_ID and CLIENT_SECRET must be defined in .env'
'CLIENT_ID and CLIENT_SECRET must be defined in .env'
This error means the backend started but couldn’t find your credentials in the environment. The most common cause is that
.env is sitting in the wrong directory, or that the Dotenv::createImmutable() call is pointing at the wrong path.'sodium_crypto_sign_verify_detached() does not exist'
'sodium_crypto_sign_verify_detached() does not exist'
This error means PHP’s sodium extension isn’t enabled. The extension ships with PHP 7.2 and later and is enabled by default on most distributions, but some minimal PHP installations (particularly Docker images and shared hosts) ship without it.Running
php -m | grep sodium will confirm whether the extension is loaded. If the command returns nothing, the extension needs to be enabled.'Failed to fetch JWT (401)' from the auth endpoint
'Failed to fetch JWT (401)' from the auth endpoint
A 401 from the auth endpoint means the credentials are not valid.If you are still using placeholder values, this is expected. Real credentials are provided after registering your app in the Partner Portal.If you have already registered:
- check for typos or extra spaces in
.env - restart the dev server after making changes
.env require a restart.CORS error in the browser console
CORS error in the browser console
A CORS error usually means the request reached the backend but the browser blocked the response because the origin didn’t match what the backend allows. The CORS middleware in
index.php is configured to allow http://localhost:5173, which matches the default Vite dev server port.If the Vite dev server is running on a different port (for example, because port 5173 was already in use and Vite picked 5174 instead), the browser will see a mismatch. Updating the Access-Control-Allow-Origin value in index.php to match the actual Vite port, or freeing up port 5173, should resolve it.Next: Connect and Fetch Data
The backend verifies session tokens and is ready to call the JTL Cloud API. The remaining work is registering your app with JTL to get real credentials, installing the app in the JTL Hub, and pulling product data from JTL-Wawi:Connect and Fetch Data
Register your app, install it in the JTL Hub, and fetch products from
JTL-Wawi.