Build the ASP.NET Core backend for a JTL Cloud App using .NET 8 andDocumentation 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.
NSec.Cryptography 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: .NET 8, ASP.NET Core Web API, NSec.Cryptography for JWT verification.
Prerequisites
You need:- ✅ A finished frontend from the Build the Frontend page, running locally on
http://localhost:5173 - ✅ .NET SDK 8.0 or higher. Run
dotnet --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 scaffold a Web API project inside it.
dotnet new webapi template includes a sample WeatherForecast controller and model. You can leave these in place or delete them. They won’t affect anything you build in this guide.
2. Install Packages
| Package | Purpose |
|---|---|
NSec.Cryptography | Cryptographic primitives for Ed25519 signature verification, used to validate JTL session tokens |
3. Set up Environment Variables
ASP.NET Core usesappsettings.json for configuration. For local secrets, use the .NET user-secrets tool so credentials never end up in source control.
From the Backend/ directory:
dotnet user-secrets set commands.
For production, swap user-secrets for environment variables, Azure Key Vault, or whichever secrets manager fits your hosting platform.
4. Build the Auth Service
The first piece of the backend is a service 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/Services/JtlAuthService.cs:
IConfiguration (which user-secrets feeds into automatically), encodes them as Basic auth, and sends a client_credentials grant request to JTL’s auth endpoint. The token is short-lived, so the method 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. JTL signs session tokens withEd25519.
Create Backend/Services/SessionVerifier.cs:
VerifyAsync method fetches the JWKS document and pulls out the first public key. The private VerifyAndDecode method splits the JWT into its three parts (header, payload, signature), reconstructs the signed data, and asks NSec to verify the signature against the public key. If the signature is valid and the token isn’t expired, it returns the decoded payload as a strongly-typed record.
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 Controller
Now wire the verifier into a controller. The frontend’s shell layout sends the session token to/api/connect-tenant and expects a tenant ID back.
Create Backend/Controllers/ConnectTenantController.cs:
X-Session-ID request header, hands it to the verifier, and returns the tenant details on success or a 401 on failure. The [ApiController] attribute handles model binding and validation automatically.
See the Tenant Mapping section for more on managing tenants in production.
7. Wire up Services and CORS
ASP.NET Core needs to know about the services it should inject and the controllers it should expose. Replace the contents ofBackend/Program.cs:
AddHttpClient<TService> does two things at once: it registers the service in the DI container and gives each instance its own HttpClient from the built-in factory.
The CORS policy 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 middleware is a useful safety net during development.
8. Configure the Backend Port
By default,dotnet new webapi listens on a random port chosen at startup, but the frontend’s Vite dev proxy expects the backend on http://localhost:5273. Pin the port in Backend/Properties/launchSettings.json by replacing the applicationUrl value in the http profile:
https, IIS Express) in place. The dotnet run command picks the http profile by default during development.
9. Run the Backend
Start the backend:401 response with {"error":"Failed to verify session token"}. That’s the expected outcome for an invalid token. The route is alive, the request was parsed, and the verifier ran and rejected the token. A real session token from the App Shell will follow the same path and succeed.
Common Issues
'Jtl:ClientId and Jtl:ClientSecret must be configured'
'Jtl:ClientId and Jtl:ClientSecret must be configured'
This error means the backend started but couldn’t find your credentials in configuration. The most common cause is that user-secrets weren’t initialised, or were set in a different directory than the project expects.User-secrets are scoped to a specific project, identified by the
UserSecretsId GUID in the .csproj file. Running dotnet user-secrets list from inside the Backend/ directory will show whether the secrets are present for this project. If the list comes back empty, running dotnet user-secrets init followed by the two dotnet user-secrets set commands from earlier in the guide will populate them.It’s also worth confirming that you ran the user-secrets commands from the Backend/ directory and not the project root. The tool resolves the target project based on the current working directory.'Failed to fetch JWT (401)' from the auth endpoint
'Failed to fetch JWT (401)' from the auth endpoint
A 401 from JTL’s auth endpoint means the credentials being sent aren’t recognised. Until you’ve registered your app in the Partner Portal, this is the expected response, since the placeholder values from earlier aren’t real credentials. The next page in this guide walks through registration and provides the real values.If you’ve already registered the app and are still seeing this error, the most likely causes are typos in the user-secrets values and forgetting to restart the backend after updating them. ASP.NET Core reads configuration once at startup, so changes to user-secrets take effect only on the next
dotnet run. The dotnet user-secrets list command is the fastest way to confirm the stored values match what the Partner Portal showed you.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 policy in
Program.cs 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 WithOrigins value in Program.cs to match the actual Vite port, or freeing up port 5173, should resolve it.Worth noting: when the Vite dev proxy is forwarding /api/* requests, the browser shouldn’t actually see CORS at all, since the requests look same-origin from the browser’s perspective. CORS errors usually appear when something is calling the backend directly on localhost:5273 instead of going through the proxy.Backend starts on a different port than 5273
Backend starts on a different port than 5273
If
dotnet run reports a different port (for example, 5000 or a random high number), the launchSettings.json change from earlier in the guide either hasn’t been saved or isn’t being picked up.Confirming that Backend/Properties/launchSettings.json contains "applicationUrl": "http://localhost:5273" in the http profile, then stopping and restarting dotnet run, should pin the port. If dotnet run is using a different profile than expected, passing --launch-profile http explicitly will force it to use the right one.An alternative if you’d rather not edit launchSettings.json is to set the ASPNETCORE_URLS environment variable: ASPNETCORE_URLS=http://localhost:5273 dotnet run. This works without any config file changes.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.