Référence d'intégration Hanko Auth

VérifiéSûr

Guide d'intégration de l'authentification Hanko auto-hébergée avec Docker Compose, config frontend et backend, vérification JWT.

Spar Skills Guide Bot
SecuriteIntermédiaire
24/07/2026
Claude Code
#hanko#authentication#self-hosted#webauthn#jwt

Recommandé pour

Synthèse automatique

Fournit des instructions pour intégrer l'authentification Hanko dans une application web avec Docker Compose, Next.js et Phoenix.

Points forts

  • Solution auto-hébergée offrant un contrôle total sur les données utilisateur
  • Prend en charge WebAuthn (passkeys), mots de passe et codes
  • Sessions basées sur JWT avec vérification JWKS
  • Architecture claire pour l'intégration frontend et backend

Limites

  • Nécessite une configuration minutieuse en production (HTTPS, cookies sécurisés)
  • Limitée à la pile technologique spécifique (Next.js, Phoenix)
  • Hanko Cloud peut être nécessaire pour OAuth tiers et domaines personnalisés
Quand l'utiliser

Lorsque vous avez besoin d'un système d'authentification auto-hébergé compatible avec les passkeys pour une application web avec un frontend JavaScript et une API backend.

Quand l'éviter

Lorsque vous avez besoin de fournisseurs OAuth prêts à l'emploi ou préférez un service d'authentification entièrement géré comme Auth0 ou Firebase.

Analyse de sécurité

Sûr
Score qualité92/100

The skill is a static reference document with no executable commands or destructive actions. It contains no secrets, no obfuscated payloads, and no instructions that could compromise a system if followed.

Aucun point d'attention détecté

Comment cette analyse est faite

Exemples

Set up Hanko with Docker Compose
I want to self-host Hanko authentication using Docker Compose. Show me the configuration for public and admin APIs, WebAuthn, session, and secrets.
Integrate Hanko into Next.js
How do I add Hanko login and registration to my Next.js app using @teamhanko/hanko-elements? Include setup for web components and events.
Verify Hanko JWT in Next.js middleware
Write a Next.js middleware that verifies the Hanko JWT using JWKS from the Hanko API.

Hanko Auth — Project Skill Reference

Self-hosted Hanko (ghcr.io/teamhanko/hanko:latest) via Docker Compose. Public API: :8000, Admin API: :8001. Frontend env var: NEXT_PUBLIC_HANKO_API_URL=http://localhost:8000. Backend env var: HANKO_API_URL=http://hanko:8000 (internal Docker network).


Architecture

Browser
  ├── <hanko-auth /> web component  →  Hanko :8000  (login / register)
  ├── Cookie "hanko" (JWT, httpOnly, SameSite=Lax)
  ├── Next.js middleware  →  verifies JWT via JWKS
  └── API calls to Phoenix :4000  →  Bearer <jwt>

Phoenix
  ├── AuthPlug  →  verify JWT with JwksCache (ETS, 5 min TTL)
  └── LoadCurrentUserPlug  →  find_or_create user by hanko_id (sub claim)

Hanko Config (hanko/config.yaml)

server:
  public:  { address: ":8000", cors: { allow_origins: ["http://localhost:3000"], allow_credentials: true } }
  admin:   { address: ":8001" }

webauthn:
  relying_party: { id: "localhost", display_name: "Repousse", origins: ["http://localhost:3000"] }

session:
  lifespan: "1h"
  enable_auth_token_header: true   # also sends token as X-Auth-Token header
  cookie: { http_only: true, same_site: "lax", secure: false }  # secure: true in prod

password:  { enabled: true, min_length: 8 }
passcode:  { ttl: 120 }

secrets:
  keys: ["<32-char-secret>"]   # rotate via array; first key used to sign

Production checklist:

  • secure: true on cookie
  • allow_origins → real domain
  • relying_party.id + origins → real domain
  • webauthn.relying_party.origins must include protocol+host (no trailing slash)
  • Custom domain in Hanko Cloud (if Cloud) fixes 3rd-party OAuth + cookie scoping

Frontend (@teamhanko/hanko-elements)

Installation

npm install @teamhanko/hanko-elements @teamhanko/hanko-frontend-sdk

Register components (client component)

"use client";
import { register } from "@teamhanko/hanko-elements";
const HANKO_API_URL = process.env.NEXT_PUBLIC_HANKO_API_URL!;

useEffect(() => { register(HANKO_API_URL).catch(console.error); }, []);

Web components

<hanko-auth />         {/* combined login + registration */}
<hanko-login />        {/* login only */}
<hanko-registration /> {/* registration only */}
<hanko-profile />      {/* credential management */}

TypeScript — add to src/custom.d.ts:

declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "hanko-auth": HankoAuthElementProps;
      "hanko-profile": HankoProfileElementProps;
      "hanko-events": HankoEventsElementProps;
    }
  }
}

Events

// DOM event (used in this project)
document.addEventListener("hankoAuthSuccess", handler);

// SDK event
import { Hanko } from "@teamhanko/hanko-frontend-sdk";
const hanko = new Hanko(HANKO_API_URL);
hanko.onSessionCreated(() => router.push("/dashboard"));

Register options

register(url, {
  shadow: true,                    // default true — shadow DOM
  injectStyles: true,              // default true
  enablePasskeys: true,            // default true
  sessionCheckInterval: 3000,      // ms minimum
  sessionTokenLocation: "cookie",  // "cookie" | "sessionStorage"
  cookieDomain: "example.com",
  cookieSameSite: "lax",
  fallbackLanguage: "fr",
});

Frontend SDK (@teamhanko/hanko-frontend-sdk)

import { Hanko } from "@teamhanko/hanko-frontend-sdk";
const hanko = new Hanko(HANKO_API_URL);

// Current user
const user = await hanko.user.getCurrent();
// { id, email, ... }

// Logout
await hanko.user.logout();

Logout pattern (this project)

// lib/auth.ts
export async function logout() {
  const hanko = getHanko();
  await hanko.user.logout();
}

Next.js Middleware (JWT via JWKS)

Cookie name: hanko — contains a signed JWT (RS256).

// middleware.ts
import { jwtVerify, createRemoteJWKSet } from "jose";

const JWKS = createRemoteJWKSet(
  new URL(`${HANKO_API_URL}/.well-known/jwks.json`)
);

const token = request.cookies.get("hanko")?.value;
await jwtVerify(token, JWKS);  // throws on invalid/expired

JWKS endpoint: GET {HANKO_API_URL}/.well-known/jwks.json

Public paths in this project: ["/", "/auth/login", "/auth/register", "/api/webhooks"]

Matcher (excludes Next.js internals):

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.svg).*)"],
};

Phoenix/Elixir — JWT Validation

Flow

  1. Frontend sends Authorization: Bearer <jwt> on all API calls
  2. AuthPlug extracts token, calls JwksCache.get_keys/0, verifies via Joken
  3. On success: conn.assigns.hanko_claims = %{"sub" => hanko_user_id, "exp" => ..., "email" => ...}
  4. LoadCurrentUserPlug finds or creates Repousse.Accounts.User by hanko_id
  5. conn.assigns.current_user available in controllers

JWT claims

%{
  "sub" => "uuid-string",    # Hanko user ID — used as hanko_id FK
  "email" => "user@ex.com",  # present on first login
  "exp" => 1234567890,       # unix timestamp
  "iss" => "http://localhost:8000"
}

JwksCache (Repousse.Auth.JwksCache)

  • GenServer, ETS table :hanko_jwks
  • Refreshes every 5 min
  • Config: Application.get_env(:repousse, :hanko)[:jwks_url] → should be http://hanko:8000/.well-known/jwks.json (internal Docker URL)
  • Start in application.ex supervision tree

Router pipeline

pipeline :authenticated do
  plug RepousseWeb.Plugs.AuthPlug
  plug RepousseWeb.Plugs.LoadCurrentUserPlug
end

pipeline :admin do
  plug RepousseWeb.Plugs.RequireRolePlug, role: :admin
end

Alternative: POST /sessions/validate (simpler, no JWT lib)

# Not used in this project but available:
Req.post("#{hanko_url}/sessions/validate",
  json: %{session_token: token}
)
# Response: %{"is_valid" => true, "user_id" => "..."}

Hanko Admin API (:8001)

No auth required from same network (internal only — never expose to internet).

Key endpoints

GET    /users                    # list users (supports ?email=, ?page=, ?per_page=)
POST   /users                    # create user
GET    /users/:id                # get user by Hanko ID
DELETE /users/:id                # delete user
PATCH  /users/:id                # update user

GET    /users/:id/credentials    # list credentials (passkeys, passwords, OTPs)
DELETE /users/:id/credentials/:cred_id

GET    /users/:id/emails         # list emails
POST   /users/:id/emails         # add email
DELETE /users/:id/emails/:email_id
POST   /users/:id/emails/:email_id/set_primary

GET    /users/:id/sessions       # active sessions
DELETE /users/:id/sessions/:session_id  # revoke session

GET    /audit_logs               # audit trail
GET    /webhooks                 # list configured webhooks
POST   /webhooks                 # create webhook

Usage from Elixir (internal Docker network):

base = Application.get_env(:repousse, :hanko)[:admin_url]
# e.g. http://hanko:8001

Req.get("#{base}/users/#{hanko_id}")
Req.delete("#{base}/users/#{hanko_id}")

Webhooks

Requires Pro/Enterprise plan on Hanko Cloud. Self-hosted: configure in config.yaml.

Payload structure

{
  "event": "user.login",
  "token": "<JWT — decode to get data>"
}

JWT payload (after decode + JWKS verify):

{
  "sub": "hanko webhooks",
  "evt": "user.login",
  "data": { /* full user object */ },
  "iat": 1234567890,
  "exp": 1234567890,
  "aud": ["..."]
}

Event types

user.create               # new registration
user.delete               # account deleted
user.login                # authentication event
user.update.email.create  # email added
user.update.email.delete  # email removed
user.update.email.primary # primary email changed
user.update.password.update
user.update.username.create / .delete / .update

Verification (Phoenix)

# 1. Fetch JWKS from {HANKO_API_URL}/.well-known/jwks.json
# 2. Verify token with JWKS (same JwksCache can be reused)
# 3. Decode payload → extract data.evt + data.data
# 4. Return 200 within 30 seconds

Passkey API (separate product — not used in this project)

Use only if adding passkeys to an existing auth system (not replacing it). This project uses full Hanko (auth + user management), so Passkey API is not relevant.

If ever needed:

  • Hanko Cloud → create "Passkey API" project type
  • Two endpoint tiers: frontend (no secret) for login, backend (secret key) for registration
  • JS SDK: @teamhanko/passkeys-sdk

Common Gotchas

| Problem | Cause | Fix | |---------|-------|-----| | Cookie not sent to Phoenix | Authorization: Bearer not set | Frontend must read cookie and send as header | | JWKS fetch fails | Wrong URL in JwksCache config | Use Docker-internal URL http://hanko:8000 not localhost | | WebAuthn fails locally | relying_party.id mismatch | Must match origin hostname exactly | | Session not created event | Using wrong event name | DOM: hankoAuthSuccess, SDK: onSessionCreated | | CORS error on login | Missing allow_credentials: true | Set in hanko/config.yaml + Phoenix CORSPlug | | Token expired (1h) | Default lifespan | Extend in config or implement refresh | | Admin API 404 | Wrong port | Admin is :8001, Public is :8000 | | Passkey not offered | enablePasskeys: false | Remove option or set to true |


Relevant Files (this project)

| File | Purpose | |------|---------| | hanko/config.yaml | Hanko server config | | docker-compose.yml | Hanko service + env vars | | frontend/src/middleware.ts | Next.js route protection (JWKS verify) | | frontend/src/lib/auth.ts | Hanko SDK singleton + logout | | frontend/src/app/(main)/auth/login/page.tsx | <hanko-auth /> component + post-login redirect | | backend/lib/repousse/auth/jwks_cache.ex | ETS-cached JWKS keys, 5 min refresh | | backend/lib/repousse_web/plugs/auth_plug.ex | JWT verification via Joken | | backend/lib/repousse_web/plugs/load_current_user_plug.ex | Hanko ID → DB user | | backend/lib/repousse_web/router.ex | Pipeline composition |

Du fichier au premier essai

Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.

Consulter la source et ses fichiers annexes

Ce téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.

1. Télécharger le fichier

Télécharger SKILL.md

Depuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/hanko-auth-integration-reference-mryiqibf/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.

Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.

mkdir -p '.skill-downloads/hanko-auth-integration-reference-mryiqibf' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/hanko-auth-integration-reference-mryiqibf/download' --output '.skill-downloads/hanko-auth-integration-reference-mryiqibf/SKILL.md.part' && mv '.skill-downloads/hanko-auth-integration-reference-mryiqibf/SKILL.md.part' '.skill-downloads/hanko-auth-integration-reference-mryiqibf/SKILL.md'

2. Vérifier les prérequis

Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.

Extrait original : Installation
npm install @teamhanko/hanko-elements @teamhanko/hanko-frontend-sdk

Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.

3. Charger la skill

Le fichier ne fournit pas un en-tête name/description exploitable pour cette procédure native. Le téléchargement reste disponible pour lecture ; utilisez le mode manuel ci-dessous.

Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.

4. Faire un premier essai

Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :

Lis .skill-downloads/hanko-auth-integration-reference-mryiqibf/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.

Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.

Skills similaires