Skip to documentation
Heimdall Docs
Heimdall Docs

Client Context & Request Hooks

Opt in to bounded browser presentation hints, resolve asynchronous request headers, and handle raw unauthorized responses without replacing Heimdall's request model.

Client information and request headers solve different problems: one is typed presentation context, the other is application-controlled transport metadata.

1. Enable Client Information

Collection is off by default and applies only to content actions. The first enabled action collects the snapshot; there is no extra network round trip.

Text
Heimdall.config.clientInfo = true;
Heimdall.config.clientInfoMaxAgeMs = 60_000;

2. Bind It Like HttpContext

HeimdallClientInfo is a framework parameter and does not consume the action's single payload slot. Binding succeeds with IsAvailable false when the browser did not send the header.

JSON
[ContentInvocation("dashboard.render")]
public static IHtmlContent Render(
    DashboardRequest request,
    HeimdallClientInfo client,
    HttpContext httpContext)
{
    var layout = client.IsAvailable && client.ViewportWidth < 720
        ? DashboardLayout.Compact
        : DashboardLayout.Default;

    return Dashboard.Render(request, layout);
}

3. Bounded Presentation Hints

The fixed model contains timezone and locale, viewport and screen size, orientation and device-pixel ratio, color and accessibility preferences, and pointer/touch/hover capabilities. DeviceCategory is only a mobile/tablet/desktop heuristic.

Text
client.TimeZone
client.Locale / client.Languages
client.ViewportWidth / client.ViewportHeight
client.ScreenWidth / client.ScreenHeight
client.Orientation / client.DevicePixelRatio
client.ColorScheme / client.PrefersReducedMotion
client.PrefersContrast / client.ForcedColors
client.Touch / client.MaxTouchPoints
client.Pointer / client.Hover / client.Online
client.DeviceCategory

4. Collection Cost and Freshness

The runtime caches one snapshot and invalidates it after resize, orientation, language, connectivity, display-preference, pointer, or hover changes. Maximum age catches timezone and UTC-offset changes without measuring every request. Use zero only when every action truly needs a fresh sample.

Text
60_000 ms default -> reuse until dirty or one minute old
0 ms              -> collect on every action attempt

Latency impact:
- synchronous browser reads
- one bounded JSON request header
- no additional HTTP request

5. Customize or Omit Per Request

The cancellable before event runs immediately before serialization. Its changes are request-local and do not mutate the cached snapshot.

JavaScript
document.addEventListener("heimdall:client-info-before", event => {
  event.detail.info.locale = getApplicationLocale();

  if (event.detail.actionId === "telemetry.ignore") {
    event.preventDefault(); // omit header, keep action
  }
});

6. Async Request Headers

Use requestHeaders when an app must obtain a JWT or other transport header before Heimdall sends a content action, CSRF request, or Bifrost token request. The callback may be synchronous or asynchronous.

JavaScript
Heimdall.config.requestHeaders = async context => {
  const token = await auth.getAccessToken({
    signal: context.signal
  });

  return token
    ? { Authorization: `Bearer ${token}` }
    : null;
};

// A simple existing token works too:
Heimdall.config.requestHeaders = () => ({
  Authorization: `Bearer ${sessionStorage.getItem("access_token")}`
});

7. Context, Precedence, and Failure

Inspect context.kind and URL when credentials should be limited to specific Heimdall endpoints. Existing framework headers are available through context.headers. Returned values win over same-name mutations. A rejected provider fails closed: the network request is not sent and the normal request-error lifecycle runs.

C#
Heimdall.config.requestHeaders = async context => {
  if (!context.url.startsWith(location.origin))
    return null;

  if (!["content-action", "csrf-token", "bifrost-token"]
        .includes(context.kind))
    return null;

  context.headers["X-App-Version"] = appVersion;
  return { Authorization: await getBearerToken(context.signal) };
};

8. Unauthorized Hook

A raw 401 response emits a cancellable heimdall:unauthorized event before Heimdall performs its normal response handling. It does not fire for 403, and an explicit server redirect remains the server's navigation instruction.

JavaScript
document.addEventListener("heimdall:unauthorized", event => {
  event.preventDefault();
  auth.openSignInDialog({ returnUrl: location.href });
});

9. Security Boundary

X-Heimdall-Client-Info is ordinary JSON protected in transit by HTTPS, not signed truth. The server caps it at 4096 characters and rejects malformed values. Treat every field as untrusted and use it only for presentation, accessibility defaults, or diagnostics hints.

Text
Never use client info for:
- authorization or entitlement
- pricing or fraud decisions
- audit identity
- permanent device identification

10. Cross-Origin Frontends

A browser-hosted frontend on another origin needs an explicit ASP.NET Core CORS policy for the Heimdall methods and headers it actually enables. Authorization and X-Heimdall-Client-Info trigger preflight; RequestVerificationToken is needed only while antiforgery is enabled. Cookie credentials are a separate decision: the current runtime uses credentials: same-origin, so do not assume cross-origin cookie authentication.

Text
Required documentation checklist:
- allow trusted frontend origins, never arbitrary origins for auth
- allow POST and GET as needed
- allow Authorization, Content-Type,
  X-Heimdall-Content-Action
- allow RequestVerificationToken when antiforgery is enabled
- allow X-Heimdall-Client-Info when client info is enabled
- place UseCors before UseHeimdall
- verify OPTIONS preflight separately