@hotosm/hanko-auth
v0.5.2
Published
Web component for HOTOSM SSO authentication with Hanko and OSM integration
Readme
Web Component: <hotosm-auth>
Lit-based web component for HOTOSM SSO authentication with Hanko and OpenStreetMap integration.
Installation
# npm
npm install @hotosm/hanko-auth
# pnpm
pnpm add @hotosm/hanko-auth
# yarn
yarn add @hotosm/hanko-authQuick Start
Import the component
import "@hotosm/hanko-auth";Use in HTML
<hotosm-auth
hanko-url="https://login.hotosm.org"
show-profile="true"
></hotosm-auth>React Example
import { useEffect, useRef } from "react";
import "@hotosm/hanko-auth";
export function AuthButton({ hankoUrl, onLogin }) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const el = ref.current;
const handler = (e: CustomEvent) => onLogin(e.detail.user);
el?.addEventListener("hanko-login", handler);
return () => el?.removeEventListener("hanko-login", handler);
}, [onLogin]);
return <hotosm-auth ref={ref} hanko-url={hankoUrl} osm-required />;
}Attributes
Core
| Attribute | Type | Default | Description |
| ----------- | ------ | ------------------------ | ------------------------------------------ |
| hanko-url | string | window.location.origin | Login service URL for Hanko authentication |
| base-path | string | "" | Base URL for OSM OAuth endpoints |
| auth-path | string | /api/auth/osm | OSM auth endpoints path |
Behavior
| Attribute | Type | Default | Description |
| ---------------- | ------- | -------------- | -------------------------- |
| osm-required | boolean | false | Require OSM connection |
| osm-scopes | string | "read_prefs" | Space-separated OSM scopes |
| auto-connect | boolean | false | Auto-redirect to OSM OAuth |
| verify-session | boolean | false | Verify session on return |
Display
| Attribute | Type | Default | Description |
| ---------------- | ------- | ---------- | ----------------------------------------------------------------- |
| show-profile | boolean | false | Show full profile (vs header button) |
| display-name | string | "" | Override display name |
| lang | string | "en" | Language/locale code (e.g., "en", "es", "fr"). Enlish as fallback |
| button-variant | string | "filled" | Button style: filled, outline, or plain |
| button-color | string | "primary"| Button color: primary, neutral, or danger |
| display | string | "default"| Display mode: default (compact avatar) or bar (full-width bar with avatar + email + arrow) |
Redirects
| Attribute | Type | Default | Description |
| ----------------------- | ------ | ------- | -------------------------- |
| redirect-after-login | string | "" | URL after successful login |
| redirect-after-logout | string | "" | URL after logout |
Cross-app
| Attribute | Type | Default | Description |
| ------------------- | ------ | ------- | ----------------------------- |
| mapping-check-url | string | "" | URL to check user mapping |
| app-id | string | "" | App identifier for onboarding |
Events
The component dispatches the following custom events:
| Event | Detail | When |
| --------------- | ---------------------- | --------------------------- |
| hanko-login | { user: HankoUser } | User logged in |
| osm-connected | { osmData: OSMData } | OSM account linked |
| osm-skipped | {} | User skipped OSM connection |
| auth-complete | {} | Auth flow complete |
| logout | {} | User logged out |
Event Handling Example
const auth = document.querySelector("hotosm-auth");
auth.addEventListener("hanko-login", (e) => {
console.log("Logged in:", e.detail.user.email);
});
auth.addEventListener("osm-connected", (e) => {
console.log("OSM connected:", e.detail.osmData.osm_username);
});
auth.addEventListener("logout", () => {
console.log("User logged out");
window.location.href = "/";
});Flash Prevention (localStorage cache)
On remount (e.g. React navigation), the component checks localStorage for a cached user under the key hotosm-auth-user. If found, it skips the loading spinner and renders immediately with the cached user.
The component reads from this key but does not write to it. The host app is responsible for keeping it in sync:
// Write on login
auth.addEventListener("hanko-login", (e) => {
localStorage.setItem("hotosm-auth-user", JSON.stringify(e.detail.user));
});
// Clear on logout
auth.addEventListener("logout", () => {
localStorage.removeItem("hotosm-auth-user");
});If the key is absent (first visit, after logout, or cleared storage), the component falls back to its normal loading flow — no change in behavior.
Usage Modes
Header Mode (default)
Shows a compact login button in the header:
<header>
<hotosm-auth
hanko-url="https://login.hotosm.org"
redirect-after-login="/"
></hotosm-auth>
</header>Button Styling
Customize the login button appearance with button-variant and button-color:
<!-- Filled primary button (default) -->
<hotosm-auth hanko-url="https://login.hotosm.org"></hotosm-auth>
<!-- Outline button -->
<hotosm-auth
hanko-url="https://login.hotosm.org"
button-variant="outline"
button-color="primary"
></hotosm-auth>
<!-- Plain text button -->
<hotosm-auth
hanko-url="https://login.hotosm.org"
button-variant="plain"
button-color="neutral"
></hotosm-auth>
<!-- Filled danger button -->
<hotosm-auth
hanko-url="https://login.hotosm.org"
button-variant="filled"
button-color="danger"
></hotosm-auth>Bar Mode
Shows a full-width bar with avatar, email, and chevron arrow (ideal for mobile drawers/menus):
<hotosm-auth
hanko-url="https://login.hotosm.org"
display="bar"
redirect-after-login="/"
></hotosm-auth>Profile Mode
Shows full authentication form (for login pages):
<hotosm-auth
hanko-url="https://login.hotosm.org"
show-profile
osm-required
auto-connect
redirect-after-login="https://portal.hotosm.org"
></hotosm-auth>Styling
The component uses Shadow DOM and can be customized using CSS custom properties.
CSS Custom Properties
Whole component
| Property | Description | Default |
| ----------------- | --------------------------------------------- | --------------------------------------- |
| --font-family | Font family for all text in the component | system-ui, -apple-system, sans-serif |
| --font-weight | Font weight for all text in the component | 500 |
Login button
| Property | Description | Default |
| ----------------------------- | ---------------------------------- | ------------------------------------------------------ |
| --login-btn-margin | Margin around the login button | 0 |
| --login-btn-padding | Padding inside the login button | var(--hot-spacing-x-small) var(--hot-spacing-medium) |
| --login-btn-bg-color | Background color of login button | var(--hot-color-primary-1000) |
| --login-btn-hover-bg-color | Background color on hover | var(--hot-color-primary-900) |
| --login-btn-border-radius | Border radius of login button | var(--hot-border-radius-medium) |
| --login-btn-text-color | Text color of login button | white |
| --login-btn-text-size | Font size of login button text | var(--hot-font-size-medium) |
| --login-btn-font-family | Font family of login button | falls back to --font-family |
| --login-btn-font-weight | Font weight of login button | falls back to --font-weight |
Example:
hotosm-auth {
/* Whole component */
--font-family: 'Inter', sans-serif;
--font-weight: 400;
/* Login button overrides */
--login-btn-margin: 8px;
--login-btn-padding: 12px 24px;
--login-btn-bg-color: #d73f3f;
--login-btn-hover-bg-color: #b83333;
--login-btn-border-radius: 8px;
--login-btn-text-color: #ffffff;
--login-btn-text-size: 16px;
--login-btn-font-family: 'Arial', sans-serif;
--login-btn-font-weight: 700;
}Configuration
Hanko URL Detection
The component detects the Hanko URL in the following priority order:
hanko-urlattribute<meta name="hanko-url" content="...">tagwindow.HANKO_URLglobal variablewindow.location.origin(fallback)
<!-- Option 1: Attribute -->
<hotosm-auth hanko-url="https://login.hotosm.org"></hotosm-auth>
<!-- Option 2: Meta tag -->
<meta name="hanko-url" content="https://login.hotosm.org" />
<hotosm-auth></hotosm-auth>
<!-- Option 3: JavaScript -->
<script>
window.HANKO_URL = "https://login.hotosm.org";
</script>
<hotosm-auth></hotosm-auth>