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
Refererheaders — 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
stateparameter in your callback handler to prevent CSRF. - Register exact redirect URIs — Bulwark rejects any
redirect_urinot on the allowlist. - The callback token is single-use. Replay attempts return
401 token_already_used.