Skip to content

Integration Patterns

The widget supports multiple integration patterns depending on your use case.

This pattern gives you full control over the authentication flow. The widget emits events, and you handle HTTP requests with your preferred auth library.

Best for:

  • SPAs with auth libraries like Auth0 SPA SDK
  • Custom authentication flows
  • Complex error handling requirements
  • Token refresh and session management

Example:

typescript
import "@authhero/widget";

const widget = document.querySelector("authhero-widget");
const loginTicket = new URLSearchParams(location.search).get("state");

widget.addEventListener("formSubmit", async (e) => {
  const { data } = e.detail;

  widget.loading = true;
  try {
    const response = await fetch(
      `/u/flow/screen?form=login&state=${loginTicket}`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ data }),
      },
    );

    const result = await response.json();

    if (result.redirect) {
      window.location.href = result.redirect;
    } else {
      widget.screen = JSON.stringify(result.screen);
    }
  } catch (error) {
    console.error("Login failed:", error);
  } finally {
    widget.loading = false;
  }
});

widget.addEventListener("buttonClick", (e) => {
  const { action, value } = e.detail;

  if (action === "social-login") {
    window.location.href = `/authorize?connection=${value}&state=${loginTicket}`;
  }
});

widget.addEventListener("linkClick", (e) => {
  window.location.href = e.detail.href;
});

2. Auto-Submit Mode

The widget automatically handles form submissions and screen transitions. With auto-navigate, it also handles social login redirects.

Best for:

  • Simple hosted login pages
  • Quick prototyping
  • Minimal JavaScript requirements

Example:

html
<authhero-widget
  api-url="/u2/screen/{screenId}"
  screen-id="identifier"
  state="your-state-token"
  auth-params='{"client_id":"test-client","redirect_uri":"https://app.example.com/callback"}'
  auto-submit="true"
  auto-navigate="true"
>
</authhero-widget>

<script>
  const widget = document.querySelector("authhero-widget");

  widget.addEventListener("flowComplete", (e) => {
    if (e.detail.redirectUrl) {
      window.location.href = e.detail.redirectUrl;
    }
  });

  widget.addEventListener("flowError", (e) => {
    console.error("Auth error:", e.detail.message);
  });
</script>

3. Auth0 SPA SDK Integration

Use the widget with Auth0's official SPA SDK for production applications.

Best for:

  • Production SPAs
  • OAuth/OIDC flows
  • Token management and refresh
  • Silent authentication

Example:

typescript
import { Auth0Client } from "@auth0/auth0-spa-js";
import "@authhero/widget";

const auth0 = new Auth0Client({
  domain: "your-tenant.authhero.com",
  clientId: "your-client-id",
  cacheLocation: "localstorage",
});

// Check if returning from login
const params = new URLSearchParams(window.location.search);
if (params.has("code") && params.has("state")) {
  // Auth0 SDK handles the callback
  await auth0.handleRedirectCallback();
  window.history.replaceState({}, document.title, "/");
}

// Check authentication
const isAuthenticated = await auth0.isAuthenticated();

if (!isAuthenticated) {
  // Start login flow
  await auth0.loginWithRedirect({
    appState: { targetUrl: window.location.pathname },
  });
} else {
  // Get user info
  const user = await auth0.getUser();
  const token = await auth0.getTokenSilently();

  console.log("Logged in as:", user);
}

If you want to embed the widget directly instead of using loginWithRedirect():

typescript
// Get login ticket from authorize endpoint
const loginTicket = await initiateLogin(); // Your custom function

// Fetch initial screen
const response = await fetch(`/u/flow/screen?form=login&state=${loginTicket}`);
const { screen, branding } = await response.json();

const widget = document.querySelector("authhero-widget");
widget.screen = JSON.stringify(screen);
widget.branding = JSON.stringify(branding);

// Handle submissions
widget.addEventListener("formSubmit", async (e) => {
  const response = await fetch(
    `/u/flow/screen?form=login&state=${loginTicket}`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ data: e.detail.data }),
    },
  );

  const result = await response.json();

  if (result.redirect) {
    // Redirect to callback - Auth0 SDK will handle it
    window.location.href = result.redirect;
  } else {
    widget.screen = JSON.stringify(result.screen);
  }
});

4. Custom Token Management

Handle tokens and sessions yourself without an auth library. Access tokens are kept in memory (never persisted to localStorage), and refresh tokens are managed via httpOnly cookies set by your backend.

Security Note

Do not store access tokens or refresh tokens in localStorage or sessionStorage. These are accessible to any JavaScript on the page, making them vulnerable to XSS attacks. Instead, keep access tokens in memory and let the backend manage refresh tokens in httpOnly, Secure, SameSite=Strict cookies.

Best for:

  • Custom authentication requirements
  • Non-standard OAuth flows
  • Direct API integration

Example:

typescript
import "@authhero/widget";

// In-memory token store — cleared on page reload, not accessible to XSS
let accessToken: string | null = null;

const tokenStorage = {
  get: () => accessToken,
  set: (token: string) => {
    accessToken = token;
  },
  clear: () => {
    accessToken = null;
  },
};

const widget = document.querySelector("authhero-widget");
const loginTicket = new URLSearchParams(location.search).get("state");

widget.addEventListener("formSubmit", async (e) => {
  const response = await fetch(
    `/u/flow/screen?form=login&state=${loginTicket}`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ data: e.detail.data }),
    },
  );

  const result = await response.json();

  if (result.redirect) {
    // Parse callback URL for code
    const url = new URL(result.redirect);
    const code = url.searchParams.get("code");

    // Exchange code for tokens via your backend.
    // The backend should set the refresh token as an httpOnly cookie
    // and return only the access token in the response body.
    const tokenResponse = await fetch("/api/auth/token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "same-origin",
      body: JSON.stringify({
        grant_type: "authorization_code",
        code,
        client_id: "your-client-id",
        redirect_uri: window.location.origin + "/callback",
      }),
    });

    const { access_token } = await tokenResponse.json();
    tokenStorage.set(access_token);

    // Redirect to app
    window.location.href = "/app";
  } else {
    widget.screen = JSON.stringify(result.screen);
  }
});

// Token refresh — the refresh token is sent automatically via the
// httpOnly cookie; only the new access token is returned in the body.
async function refreshAccessToken() {
  const response = await fetch("/api/auth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    credentials: "same-origin",
    body: JSON.stringify({
      grant_type: "refresh_token",
      client_id: "your-client-id",
    }),
  });

  const { access_token } = await response.json();
  tokenStorage.set(access_token);

  return access_token;
}

Backend token endpoint

Your /api/auth/token endpoint should proxy to the AuthHero /oauth/token endpoint. On a successful response containing a refresh_token, the backend strips it from the JSON body and sets it as a cookie:

Set-Cookie: refresh_token=<token>; HttpOnly; Secure; SameSite=Strict; Path=/api/auth; Max-Age=2592000

On refresh requests, the backend reads the cookie and forwards it to /oauth/token.

5. Generic Forms (Non-Auth)

The widget can also be used for generic server-driven forms outside of authentication.

Best for:

  • Multi-step forms
  • Dynamic forms based on user input
  • Survey flows
  • Onboarding wizards

Example:

typescript
import "@authhero/widget";

const widget = document.querySelector("authhero-widget");

// Initial form screen
widget.screen = JSON.stringify({
  title: "Contact Us",
  description: "We'd love to hear from you",
  components: [
    {
      component: "text-input",
      id: "name",
      name: "name",
      label: "Your Name",
      required: true,
    },
    {
      component: "text-input",
      id: "email",
      name: "email",
      label: "Email Address",
      type: "email",
      required: true,
    },
    {
      component: "submit-button",
      id: "submit",
      label: "Continue",
    },
  ],
});

widget.addEventListener("formSubmit", async (e) => {
  const { data } = e.detail;

  // Send to your backend
  const response = await fetch("/api/contact", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });

  const result = await response.json();

  // Show next screen (e.g., thank you message)
  widget.screen = JSON.stringify(result.nextScreen);
});

Released under the MIT License.