Olympus Docs
InternalsHera

Hera, route map

Every page route and API route in Hera

Hera has 10 page routes, the user-visible login/consent/recovery experience. Internal API routes are minimal; Hera mostly proxies to Kratos and Hydra.

Page routes

PathSourcePurpose
/loginsrc/app/login/page.tsxLogin form (Kratos login flow renderer). Accepts login_challenge from Hydra.
/registrationsrc/app/registration/page.tsxRegistration form.
/recoverysrc/app/recovery/page.tsxPassword recovery form. Submits email and recovery token.
/verificationsrc/app/verification/page.tsxEmail verification page.
/settingssrc/app/settings/page.tsxUser settings (password, MFA, profile).
/consentsrc/app/consent/page.tsxOAuth2 consent screen (Hydra consent challenge).
/logoutsrc/app/logout/page.tsxRP-initiated logout confirmation.
/oidc-callbacksrc/app/oidc-callback/page.tsxReturn from a social IdP (Google, GitHub, etc.).
/account-linkingsrc/app/account-linking/page.tsxSocial-to-password linking confirmation.
/errorsrc/app/error/page.tsxGeneric error page for Kratos / Hydra failures.

API routes

PathMethodPurpose
/api/captcha/verifyPOSTServer-side Turnstile validation.
/api/breach-checkPOSTHIBP k-anonymity password breach check (SDK wrapper).
/api/social-providersGETList enabled OIDC providers for rendering social buttons.

Hera deliberately doesn't expose more API surface, flows happen against Kratos and Hydra directly from the browser.

Why this shape

Hera is a thin rendering layer over Kratos and Hydra. Most "logic" Hera implements is:

  • Reading a flow from Kratos.
  • Rendering the UI nodes Kratos returns.
  • POST-ing the user's submission back to Kratos.
  • (For OAuth2) accepting/rejecting Hydra's challenges.

This thinness means Hera is portable, different theming, different per-tenant branding, different UI frameworks. The auth logic stays in Kratos / Hydra.

Flow rendering

The <SchemaForm> component (from Canvas) takes Kratos's ui.nodes[] and renders the form. Adding a trait to the identity schema automatically adds a form field, no Hera code change.

// src/app/login/page.tsx
import { SchemaForm } from "@olympusoss/canvas";

export default async function Login({ searchParams }) {
  const flow = await getFlow(searchParams.flow);
  return (
    <SchemaForm
      schema={flow.ui.nodes}
      action={flow.ui.action}
      messages={flow.ui.messages}
      onSubmit={async (data) => {/* POST to Kratos */}}
    />
  );
}

Olympus-specific additions

Beyond standard Kratos flow rendering:

  • Brute-force check before passing to Kratos (via the SDK).
  • Captcha verification server-side via /api/captcha/verify.
  • Breached-password check during registration / password change.
  • Pre-linking confirmation for social → password account linking.
  • Analytics, PKCE usage, social provider usage (logged for monitoring).

These are gated by feature flags in the settings vault:

  • hera.captcha_enabled (per-flow toggle)
  • hera.breach_check_enabled

/consent reads the consent challenge from Hydra:

const challenge = searchParams.consent_challenge;
const consent = await fetch(`${hydraAdmin}/admin/oauth2/auth/requests/consent?consent_challenge=${challenge}`);

The consent UI shows the requesting client + scopes. The user accepts; Hera calls acceptOAuth2ConsentRequest on Hydra.

For first-party clients with metadata.skip_consent = true, Hera auto-accepts without rendering, see Cookbook, Brand consent screen.

State across the OAuth2 flow

Hera doesn't need cookies for OAuth2, Hydra's challenge ID encodes everything. But Hera does maintain:

  • The Kratos session cookie (for the user's identity).
  • A flow-specific CSRF cookie (Kratos manages).
  • Per-flow code_verifier if Hera initiated the auth (rare; usually the consumer app holds the verifier).

Where the analytics events go

process.stdout.write(JSON.stringify({
  type: "audit",
  event: "pkce_flow_completed",
  client_id: "...",
  ts: new Date().toISOString(),
}) + "\n");

These appear in Hera's stdout, picked up by your log shipper. See Operate, Logs and observability.

On this page