Hosted Login

Bulwark provides two models for presenting authentication UI to your users.


Embedded components vs. hosted pages

| | Embedded | Hosted | |---|---------|--------| | Implementation | <SignIn /> / <SignUp /> React components | Redirect to a Bulwark-hosted URL | | JavaScript required | Yes (React app) | No | | Customization | Full (appearance API + CSS) | Branding via dashboard (logo, colors) | | Suitable for | SPAs, Next.js apps | Multi-tenant SaaS, native apps, no-JS environments | | Token delivery | In-process | Query parameter redirect |

Use embedded components when you control the frontend. Use hosted pages when you want Bulwark to handle the UI entirely, or when you are building a native mobile app or CLI tool.


Hosted page URLs

https://api.bulwarkauth.com/hosted/{slug}/login
https://api.bulwarkauth.com/hosted/{slug}/signup

{slug} is your application's unique slug, visible in the dashboard under Applications → Settings.

Initiating a hosted login

Redirect the user to:

https://api.bulwarkauth.com/hosted/myapp/login?redirect_uri=https://myapp.com/auth/callback

Required query parameters:

| Parameter | Description | |-----------|-------------| | redirect_uri | Where to send the user after authentication. Must match a registered redirect URI in your application settings. |

Optional query parameters:

| Parameter | Description | |-----------|-------------| | state | Opaque value passed through unchanged. Use for CSRF protection or to preserve application state. | | hint | Pre-fill the email field ([email protected]) | | screen | Start on login (default) or signup |


Callback

After successful authentication, Bulwark redirects to your redirect_uri with the token in the URL fragment (after #):

https://myapp.com/auth/callback#token=eyJhbGci...

Why fragment? URL fragments are never sent to the server in HTTP requests, never logged by proxies, and never included in Referer headers — preventing token leakage.

Extract the token client-side, then exchange it on your server:

// app/auth/callback/page.tsx (client component)
"use client";
import { useEffect } from "react";

export default function Callback() {
  useEffect(() => {
    const hash = window.location.hash.substring(1);
    const token = new URLSearchParams(hash).get("token");
    if (token) {
      // Send to your server-side route for exchange
      fetch("/api/auth/exchange", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ token }),
      }).then(() => window.location.href = "/");
    }
  }, []);
  return <div>Signing in...</div>;
}

Server-side exchange:

// app/api/auth/exchange/route.ts
import { NextRequest } from "next/server";

export async function POST(req: NextRequest) {
  const { token } = await req.json();

  // Verify and exchange the hosted login token
  const res = await fetch("https://api.bulwarkauth.com/api/v1/auth/exchange", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token }),
  });

  const { access_token, refresh_token } = await res.json();

  // Set cookies, session, etc.
  // ...

  redirect("/dashboard");
}

The token in the callback URL is single-use and expires after 60 seconds. Exchange it immediately.


Branding

Hosted pages load branding from your application's settings. Configure via the dashboard or API:

PUT /api/v1/settings/applications/{id}/branding
Authorization: Bearer <admin-token>
X-Bulwark-Tenant: <tenant-id>

{
  "logo_url": "https://cdn.myapp.com/logo.png",
  "primary_color": "#6366f1",
  "background_color": "#0f172a",
  "app_name": "Acme"
}

| Field | Description | |-------|-------------| | logo_url | Logo displayed at the top of the form | | primary_color | Button and link color (hex) | | background_color | Page background color (hex) | | app_name | Shown in the page title and email subjects |


Social login

When Google or GitHub OAuth is enabled for your application (via Applications → Social Providers), the corresponding sign-in buttons appear automatically on the hosted login page. No additional configuration is needed on the hosted page itself.


Footer

Hosted pages display a "Secured by Bulwark" footer by default. This can be disabled on paid plans via the dashboard under Applications → Branding → Hide Bulwark branding.


Security considerations

  • Always validate the state parameter in your callback handler to prevent CSRF.
  • Register exact redirect URIs — Bulwark rejects any redirect_uri not on the allowlist.
  • The callback token is single-use. Replay attempts return 401 token_already_used.