Stirling-Tools/Stirling-PDF · warning · Error

No authentication token available

Error message

No authentication token available

What it means

Thrown by requireToken() in the desktop billing service when authService.getAuthToken() resolves to a falsy value. The user is not signed in, so there is no desktop JWT to attach as the Bearer header for the Stripe edge-function calls (create-checkout-session / create-customer-portal-session).

Source

Thrown at frontend/editor/src/desktop/services/billing.ts:42

export type {
  CheckoutParams,
  CheckoutSession,
  PortalParams,
  PortalSession,
} from "@cloud/services/billing";

/**
 * Deep-link the SaaS billing backend uses as Stripe's success/cancel/return
 * URL on desktop. The OS routes it back to the running app, which the deep-link
 * handler picks up to refresh the wallet after checkout/portal.
 */
const DESKTOP_BILLING_RETURN_URL = "stirlingpdf://billing/return";

/** Resolve the desktop JWT, throwing a friendly error when signed out. */
async function requireToken(): Promise<string> {
  const token = await authService.getAuthToken();
  if (!token) {
    throw new Error("No authentication token available");
  }
  return token;
}

/**
 * Create a Stripe Checkout Session for the PAYG subscription via the
 * {@code create-checkout-session} edge function (see StripeCheckoutPanel),
 * routed through Tauri (explicit bearer, deep-link callback). The Tauri webview
 * has no CSP, so the component mounts the embedded Stripe iframe from the
 * returned clientSecret (falling back to the hosted url otherwise).
 */
export async function createCheckoutSession(
  params: CheckoutParams,
): Promise<CheckoutSession> {
  const token = await requireToken();

  const { data, error } = await supabase.functions.invoke<{
    client_secret?: string;

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Gate the billing UI behind an authenticated check and prompt sign-in before calling these functions.
  2. Call authService.ensureSession() / await a valid token before invoking billing, retrying once after a refresh.
  3. If the token genuinely expired, route the user to the login screen before billing.

Example fix

// before
const session = await createCheckoutSession(params);

// after: ensure a token exists first
const token = await authService.getAuthToken();
if (!token) { navigateToLogin(); return; }
const session = await createCheckoutSession(params);
Defensive patterns

Strategy: validation

Validate before calling

const token = await authService.getAuthToken();
if (!token) { navigateToLogin(); return; }

Type guard

function isMissingToken(e: unknown): e is Error {
  return e instanceof Error && e.message === 'No authentication token available';
}

Try / catch

try { await createCheckoutSession(params); }
catch (e) {
  if (isMissingToken(e)) { navigateToLogin(); return; }
  throw e;
}

Prevention

When it happens

Trigger: Calling createCheckoutSession() or createPortalSession() while signed out: no token in the Tauri store, the session expired and refresh failed, or logout just ran. getAuthToken() returns null/empty.

Common situations: Billing/wallet UI rendered before login completes; JWT expired and the refresh path failed silently; user navigated to upgrade flow after signing out in another window.

Understand the failure class

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/670a32cdc0d5af0a. Report an issue: GitHub.