Skip to main content
JWT (JSON Web Tokens) are stateless, time-limited tokens for API authentication. Unlike API keys (long-lived), JWTs expire quickly and are refreshed on demand.

JWT vs API Keys

AspectJWTAPI Key
LifespanShort (1 hour)Long (months/years)
RefreshAuto via refresh tokenManual rotation
StorageMemory (not on disk)Secure storage
StatelessYes (server doesn’t track)No (server tracks)
Use CaseAPIs, web appsIntegrations, bots
When to use JWT:
  • Web/mobile apps
  • Temporary access
  • Cross-service authentication
  • External APIs
When to use API Key:
  • Server-to-server integrations
  • Long-lived services
  • Third-party integrations

JWT Structure

JWTs have three parts: header.payload.signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Header   │ Payload   │ Signature
────────────────────────────────────
Specifies algorithm and type:
{
  "alg": "HS256",   // HMAC SHA-256
  "typ": "JWT"
}

Payload

Contains claims (data):
{
  "sub": "user-123",           // subject (who)
  "name": "Alice",
  "email": "alice@example.com",
  "iat": 1516239022,           // issued at (when)
  "exp": 1516242622,           // expiration (when)
  "aud": "api.noorle.com",     // audience (for who)
  "scope": "read execute"      // permissions
}

Signature

Ensures token wasn’t tampered with:
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)

Token Lifecycle

Step 1: Obtain Access Token

Typically via OAuth device flow or other auth method:
POST https://api.noorle.com/oauth/token

Response:
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,        // seconds until expiration
  "refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}

Step 2: Use in API Calls

Include in Authorization header:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
     https://api.noorle.com/agents

Step 3: Token Expires

Server validates token signature and expiration:

Step 4: Refresh Token

Exchange refresh token for new access token:
POST https://api.noorle.com/oauth/token

Body:
{
  "grant_type": "refresh_token",
  "refresh_token": "eyJhbGciOiJIUzI1NiIs...",
  "client_id": "noorle-cli"
}

Response:
{
  "access_token": "new-eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "new-eyJhbGciOiJIUzI1NiIs...",
  "expires_in": 3600
}

Token Validation

Server validates tokens before processing requests:

Example: Web App Flow

// 1. User logs in via OAuth
const authCode = await userLogin();

// 2. Get tokens
const response = await fetch("https://api.noorle.com/oauth/token", {
  method: "POST",
  body: JSON.stringify({
    grant_type: "authorization_code",
    code: authCode,
    client_id: "my-web-app"
  })
});

const { access_token, refresh_token, expires_in } = await response.json();

// 3. Store tokens
sessionStorage.setItem("access_token", access_token);
sessionStorage.setItem("refresh_token", refresh_token);
sessionStorage.setItem("expires_at", Date.now() + expires_in * 1000);

// 4. Make API calls
async function callAPI(endpoint) {
  let token = sessionStorage.getItem("access_token");

  // Check if token expired
  if (Date.now() > sessionStorage.getItem("expires_at")) {
    token = await refreshToken();
  }

  return fetch(`https://api.noorle.com${endpoint}`, {
    headers: {
      "Authorization": `Bearer ${token}`
    }
  });
}

async function refreshToken() {
  const response = await fetch("https://api.noorle.com/oauth/token", {
    method: "POST",
    body: JSON.stringify({
      grant_type: "refresh_token",
      refresh_token: sessionStorage.getItem("refresh_token"),
      client_id: "my-web-app"
    })
  });

  const { access_token, refresh_token, expires_in } = await response.json();

  sessionStorage.setItem("access_token", access_token);
  sessionStorage.setItem("refresh_token", refresh_token);
  sessionStorage.setItem("expires_at", Date.now() + expires_in * 1000);

  return access_token;
}

// 5. Use in requests
const agents = await callAPI("/agents");
// Auto-refreshes token if needed

Token Claims

Standard and custom claims in the payload:
{
  "iss": "https://auth.noorle.com",    // issuer
  "sub": "user-123",                   // subject (user ID)
  "aud": "api.noorle.com",             // audience
  "iat": 1711179600,                   // issued at
  "exp": 1711183200,                   // expiration
  "nbf": 1711179600,                   // not before
  "jti": "unique-token-id",            // JWT ID (for revocation)

  // Custom claims
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "account_id": "account-123",
  "scope": "read execute",
  "roles": ["developer", "user"]
}

Token Revocation

Revoke tokens before expiration if needed:
POST https://api.noorle.com/oauth/revoke

Body:
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type_hint": "access_token"
}
When to revoke:
  • User logs out
  • Token compromised
  • User permissions changed
  • Service terminated
Revocation adds token to blocklist (checked on validation).

Security Best Practices

1. Never Store on Disk

// BAD: Token on disk
localStorage.setItem("token", accessToken);
// If browser storage compromised, token leaked

// GOOD: Token in memory only (lost on page reload)
window.token = accessToken;

// BETTER: Token in memory + refresh flow
// Token lost on reload, but refresh token in secure cookie
document.cookie = "refresh_token=...; HttpOnly; Secure; SameSite=Strict";

2. Use Secure HTTP Only Cookies

For refresh tokens (not access tokens):
Set-Cookie: refresh_token=eyJhb...; HttpOnly; Secure; SameSite=Strict

                          Not accessible  Secure transport
                          from JavaScript (HTTPS only)
            └─ Browser sent auto with requests
Benefits:
  • Browser automatically includes with requests
  • JavaScript can’t access (protects from XSS)
  • Secure flag ensures HTTPS only

3. Short Expiration

Access tokens should expire quickly:
Access token: 1 hour
  └─ If leaked, damage limited to 1 hour

Refresh token: 30 days
  └─ Longer-lived but more protected
     (in HTTP-only cookie, not JavaScript)

4. HTTPS Only

Never send JWTs over unencrypted HTTP:
HTTP  → INSECURE (token in plaintext)
HTTPS → SECURE (TLS encryption)

5. Validate Signature

Always validate token signature on the server:
// BAD: Trust token without validation
const payload = JSON.parse(atob(token.split('.')[1]));
// Attacker can forge any payload

// GOOD: Validate signature
const verified = jwt.verify(token, secret);
// Only valid if signed with correct secret

Token Expiration Times

Recommended settings:
Short-lived (sensitive operations):
  ├─ Access token: 15 minutes
  └─ Refresh token: 7 days

Standard (web apps):
  ├─ Access token: 1 hour
  └─ Refresh token: 30 days

Long-lived (trusted apps):
  ├─ Access token: 8 hours
  └─ Refresh token: 90 days

Troubleshooting

ProblemSolution
”invalid_token”Token signature invalid. Token was tampered with.
”expired_token”Token expired. Use refresh_token to get new access_token.
”invalid_scope”Token doesn’t have required permission. Request new token with correct scope.
”invalid_audience”Token is for different service. Verify token and audience match.

Next: Explore Connected Apps for third-party integrations.