Skip to content
Worldview
Get started

Authentication

Zitadel JWT, Bearer token usage, token refresh, and the dev-login endpoint.

On this page (6)

Worldview uses Zitadel as its identity provider. The S9 API Gateway validates JWTs using Zitadel's JWKS endpoint and issues short-lived internal JWTs for backend-to-backend calls.

Token types

TokenIssued byLifetimeUsed for
Zitadel access tokenZitadel OIDC1 hourAll authenticated API calls
Worldview dev tokenPOST /v1/auth/dev-login1 hourLocal development only
Refresh tokenZitadel session30 daysSilently refresh the access token

OIDC PKCE flow (production)

For production applications, use the standard OAuth 2.0 PKCE flow:

// 1. Generate a code verifier and challenge
const codeVerifier = crypto.randomUUID().replace(/-/g, "") + crypto.randomUUID().replace(/-/g, "");
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await crypto.subtle.digest("SHA-256", data);
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
  .replace(/\+/g, "-")
  .replace(/\//g, "_")
  .replace(/=/g, "");
 
// 2. Redirect to Zitadel authorization endpoint
const authUrl = new URL("https://auth.worldview.app/oauth/v2/authorize");
authUrl.searchParams.set("client_id", process.env.NEXT_PUBLIC_ZITADEL_CLIENT_ID!);
authUrl.searchParams.set("redirect_uri", "http://localhost:3000/auth/callback");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("scope", "openid profile email");
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
window.location.href = authUrl.toString();

Dev-login endpoint

For local development and testing, use the convenience endpoint:

// POST /v1/auth/dev-login
const response = await fetch("http://localhost:8000/v1/auth/dev-login", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "dev@example.com" }),
});
 
const { access_token, expires_in } = await response.json();
// Store in memory — never in localStorage

Dev-login is not for production

The dev-login endpoint is disabled in production. It is only available when DEV_LOGIN_ENABLED=true is set in the API Gateway environment. Never deploy with this flag enabled.

Using the token

Pass the token as a Bearer header on every authenticated request:

const data = await fetch("http://localhost:8000/v1/instruments/search?q=AAPL", {
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
});

Token refresh

Tokens expire after 1 hour. Use the Zitadel refresh token flow to obtain a new access token silently:

POST https://auth.worldview.app/oauth/v2/token
Content-Type: application/x-www-form-urlencoded
 
grant_type=refresh_token
&refresh_token=<refresh_token>
&client_id=<client_id>

The response includes a new access_token and optionally a new refresh_token (rotated).

JWT claims

The access token is a RS256-signed JWT. Relevant claims:

ClaimDescription
subZitadel user ID
emailUser's email address
tenant_idWorldview tenant UUID
expExpiry timestamp (Unix)
iatIssued-at timestamp (Unix)

Was this page helpful?

Last updated