JLS
Developer Docs

Access Tokens & Verification

How to generate tokens, use them in requests, and verify incoming tokens in your application.

Generating a Token#

Before you can generate a token, you need a registered app and an active access grant. If you haven't done this yet, see the Registering Your App guide.

To generate a token:

  1. Go to Dashboard → Applications → your app
  2. Find the grant in the Outbound tab
  3. Click "Generate Token"
  4. Copy the JWT immediately — it's shown only once

Using a Token in Requests#

Send the JWT in the Authorization header as a Bearer token:

GET /api/orders HTTP/1.1
Host: inventory.internal.jlstradingco.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

In code, this looks like:

const response = await fetch("https://inventory.internal.jlstradingco.com/api/orders", {
  headers: {
    "Authorization": `Bearer ${YOUR_TOKEN}`,
  },
});

What's Inside the Token#

Each JWT contains these claims. You don't need to decode the token yourself (the verification library does this), but it helps to understand what's in there:

{
  "iss": "https://eds.jlstradingco.com",
  "sub": "a1b2c3d4-...",
  "aud": "e5f6g7h8-...",
  "app_name": "Scheduling Tool",
  "iat": 1713200000,
  "exp": 1744800000
}
NameTypeRequiredDescription
issstringYesIssuer — always the EDS base URL. Verifying this confirms the token came from your EDS deployment.
substring (UUID)YesSubject — the unique ID of the app that is making the request (the calling app).
audstring (UUID)YesAudience — the unique ID of the app this token is intended for (your app, if you're verifying). Always check this matches your app ID.
app_namestringYesHuman-readable name of the calling app. Useful for logging and audit trails, but don't use it for authorization decisions.
iatnumberYesIssued-at timestamp (Unix seconds). When the token was created.
expnumberYesExpiration timestamp (Unix seconds). Tokens expire 1 year after issuance by default.

Verifying Tokens in Your App#

If other apps are calling your app with tokens, you need to verify those tokens are genuine and intended for you. This is done with the jose library and the EDS public key.

Step 1: Install the jose library

npm install jose

Step 2: Create a verification helper

Create this once and reuse it across your routes:

// lib/verify-eds-token.ts
import { createRemoteJWKSet, jwtVerify } from "jose";

// This fetches the public key from EDS and caches it automatically.
// It only re-fetches when it encounters an unknown key ID (during key rotation).
const JWKS = createRemoteJWKSet(
  new URL("/.well-known/jwks.json", process.env.EDS_BASE_URL || "https://eds.jlstradingco.com")
);

// Your app's ID from the App Registry (found in your app's detail page URL)
const MY_APP_ID = process.env.EDS_APP_ID!;

export interface VerifiedApp {
  appId: string;      // The calling app's ID (sub claim)
  appName: string;    // The calling app's human-readable name
}

export async function verifyAppToken(token: string): Promise<VerifiedApp> {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: process.env.EDS_BASE_URL || "https://eds.jlstradingco.com",
    audience: MY_APP_ID,
  });

  return {
    appId: payload.sub!,
    appName: payload.app_name as string,
  };
}

Step 3: Use it in your routes

Here's how to use the helper in a Next.js API route, an Express route, or any HTTP handler:

// Example: Next.js API route
import { NextRequest, NextResponse } from "next/server";
import { verifyAppToken } from "@/lib/verify-eds-token";

export async function GET(request: NextRequest) {
  // 1. Extract the token from the Authorization header
  const authHeader = request.headers.get("authorization");
  if (!authHeader?.startsWith("Bearer ")) {
    return NextResponse.json(
      { error: "Missing or invalid Authorization header" },
      { status: 401 }
    );
  }

  // 2. Verify the token
  try {
    const callingApp = await verifyAppToken(authHeader.slice(7));
    console.log(`Request from: ${callingApp.appName} (${callingApp.appId})`);

    // 3. Your app decides what this caller can do
    //    The token only proves identity authorization is up to you.
    return NextResponse.json({ data: "your response here" });
  } catch (err) {
    // Token is invalid, expired, or not meant for this app
    return NextResponse.json(
      { error: "Invalid or expired token" },
      { status: 401 }
    );
  }
}
// Example: Express middleware
import { verifyAppToken } from "./lib/verify-eds-token";

async function requireAppAuth(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing token" });
  }

  try {
    req.callingApp = await verifyAppToken(authHeader.slice(7));
    next();
  } catch {
    return res.status(401).json({ error: "Invalid token" });
  }
}

// Use it:
app.get("/api/orders", requireAppAuth, (req, res) => {
  console.log(`Called by: ${req.callingApp.appName}`);
  res.json({ orders: [] });
});

What Verification Checks#

When you call jwtVerify, it automatically checks:

  • Signature — the token was actually signed by EDS (not forged or tampered with)
  • Expiration— the token hasn't expired
  • Issuer — the token was issued by your EDS deployment (not some other system)
  • Audience — the token was meant for your app (not a different app)

If any of these checks fail, jwtVerifythrows an error. You don't need to check these manually.

Authorization Is Up to You#

The token proves who is calling — it does not define what they can do. After verifying the token, your app decides how to handle the request. Some examples:

  • *Allow any verified app — if a token is valid and addressed to you, let the request through. Simplest approach, good for read-only APIs.
  • *Allowlist specific apps — check the sub (calling app ID) against a list of approved app IDs. Good for sensitive operations.
  • *Different permissions per app — use the sub to look up what the calling app is allowed to do. Most flexible, requires maintaining a permissions map.

Token Lifecycle#

How long do tokens last?

Tokens expire 1 year after issuance. This is intentionally long because these are service-to-service credentials, not user sessions.

Can I regenerate a token?

Yes. Click "Generate Token" on the same grant again. The old token remains valid until it expires — generating a new token does not invalidate old ones.

What happens when a grant is revoked?

No new tokens can be generated for that grant. However, tokens already issued remain valid until they expire. This is a known trade-off of local verification — there's no central server to invalidate tokens in real time.

What if I lose a token?

Generate a new one. There's no way to retrieve a previously displayed token.

JWKS Endpoint Reference#

Public key for token verification — no authentication required

Best Practices#

  • *Always verify the audience claim. Pass your app ID as the audience option to jwtVerify. This prevents a token meant for App X from being accepted by App Y. The code examples above do this automatically.
  • *Log the calling app. After verification, log sub (app ID) and app_name. This creates an audit trail of which apps are calling yours and when.
  • *Return clear error messages. When verification fails, return a 401 with a message like "Invalid or expired token" so the calling app knows to regenerate. Avoid leaking internal details.
  • *Store tokens securely. Use environment variables or a secrets manager for the tokens you send. Never commit tokens to source code or share them in chat.
  • *Set up your app ID as an environment variable. Store your registered app ID as EDS_APP_ID in your environment. This keeps your verification code portable across environments (dev, staging, production).

Troubleshooting#

"JWSSignatureVerificationFailed"

The token's signature doesn't match the public key. This usually means the token was issued by a different EDS instance than the one you're verifying against. Check that your EDS_BASE_URL points to the correct EDS deployment.

"JWTExpired"

The token has passed its expiration date. The calling app needs to generate a new token from their grant in the dashboard.

"JWTClaimValidationFailed" with audience mismatch

The token was meant for a different app. The calling app generated a token targeting the wrong app ID. They need to create a grant targeting your app and generate a new token.

JWKS fetch fails

The createRemoteJWKSetfunction can't reach the JWKS endpoint. Verify your app can access the EDS host. After the initial fetch, keys are cached, so this only affects the first request or key rotation events.