@jtl-software/cloud-apps-auth for app token verification.
Prerequisites
You need:- ✅ A finished frontend from the Build the Frontend page, running locally on
http://localhost:5173 - ✅ Node.js v24.16.0 or higher (current LTS, includes
npm). Verify withnode --versionandnpm --version
What you’re Building
During setup, your backend verifies that the app token from your frontend is valid and was issued by JTL for your app. To do this:- Your backend fetches the identity provider’s public keys (JWKS)
- Verifies the token’s signature, issuer, and expiry against them
- Confirms the token’s audience is your app, so a token minted for another app is rejected
1. Set up the Project
Create abackend folder alongside the existing frontend folder, then initialize it.
2. Install Packages
3. Configure TypeScript
Createbackend/tsconfig.json:
NodeNext) with strict type checking. The outDir and rootDir settings keep compiled output separate from source files when you eventually build for production.
4. Set up Environment Variables
The backend needs four values.CLIENT_ID and CLIENT_SECRET are the credentials JTL issues when you create your app. JTL_APP_ID is your app’s ID, which an app token’s audience must contain. JTL_ISSUER is the identity provider that issues app tokens. You will get the real values from the Partner Portal in the next page. For now, create the file with placeholder values so the rest of the setup works.
Create backend/.env:
/api/* requests to localhost:5273, so the PORT value matches that target.
Add .env to backend/.gitignore so the secrets don’t end up in version control:
5. 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. Your backend uses it to make tenant-scoped calls to the JTL Cloud API. Createbackend/src/jtl-auth.ts:
client_credentials grant request to JTL’s auth endpoint, and returns the resulting access token. The function fetches a fresh one on each call. For higher-traffic backends you’d add caching. See Service Account Authentication.
6. Build the Token Verifier
The backend verifies the app token before trusting anything in it.verifyAppToken fetches the identity provider’s public keys, checks the signature, issuer, and expiry, then confirms the token was minted for your app.
Create backend/src/verify-app-token.ts:
verifyAppToken runs three checks and returns a breakdown rather than throwing, so you can report which one failed:
result.valid is true only when all three pass. The key set is fetched on first use and cached, so repeated verifications do not hit the network.
7. Build the Connect Tenant Endpoint
Now connect the verifier into an Express route. The frontend’s shell layout sends the app token to the backend as a Bearer token and expects a claim with the tenant information including tenant ID, client ID, etc. Createbackend/src/server.ts:
vite.config.ts already routes frontend fetch('/api/...') calls to this backend, but the CORS middleware is a useful safety net during development and stays out of the way in production.
See Tenant Mapping for more on managing tenants in production.
8. Add Run Scripts
Openbackend/package.json and replace the scripts block:
dev script uses tsx watch to run TypeScript directly, restarting the server when any source file changes. The --env-file=.env flag loads environment variables natively without needing dotenv. The build and start scripts compile to JavaScript and run the compiled output for production.
Also update the "type": "commonjs" to "type": "module" below the scripts in the package.json file so that Node can treat .js files as ES modules.
9. Run the Backend
Start the dev server:401 response with {"error":"Failed to verify app token"}. A real app token from the App Shell will follow the same path and succeed.
Common Issues
'Cannot find module ./jtl-auth.js' or similar import error
'Cannot find module ./jtl-auth.js' or similar import error
This error usually means TypeScript and Node are resolving modules differently.With
"type": "module" in package.json and "module": "NodeNext" in tsconfig.json, Node expects ES module imports to include file extensions. Even if your source file is jtl-auth.ts, the import must use ./jtl-auth.js.TypeScript resolves this correctly during development, and Node finds the compiled .js file at runtime.If you prefer not to use .js extensions, switch to "module": "CommonJS" in tsconfig.json and remove "type": "module" from package.json.'CLIENT_ID and CLIENT_SECRET must be defined in .env'
'CLIENT_ID and CLIENT_SECRET must be defined in .env'
This means the backend started without loading your environment variables.The most common cause is running Node without the
--env-file=.env flag. In that case, the .env file exists but is never read.The dev and start scripts already include this flag. If you’re running the server manually, add it back or use npm run dev.Also confirm that the .env file is inside the backend/ directory. The path is resolved relative to where Node is executed.'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.'Failed to verify app token' with real credentials
'Failed to verify app token' with real credentials
Verification checks four things, and the audience check is the one most often wrong.
JTL_APP_ID is the app ID shown in the Partner Portal under your app, not the client ID.If the audience is correct, confirm JTL_ISSUER matches the environment your app is registered in. A token issued by one environment does not verify against another’s keys.App tokens are also valid for about an hour, so a token captured earlier during debugging will fail.CORS error in the browser console
CORS error in the browser console
This means the browser blocked the response due to an origin mismatch.The backend allows requests from
http://localhost:5173, which is the default Vite dev server port. If Vite runs on a different port (for example, 5174), the request will be rejected.Update the origin in server.ts to match the actual port, or restart Vite on 5173.If you’re using the Vite dev proxy for /api/*, CORS should not appear. Seeing this error usually means the request is being made directly to the backend instead of going through the proxy.Next: Connect and Fetch Data
The backend verifies app 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.