DEV Community

Alex Spinov
Alex Spinov

Posted on

Hanko Has a Free API: Passwordless Authentication with Passkeys for Any Web App

What is Hanko?

Hanko is an open-source authentication solution focused on passkeys — the FIDO2/WebAuthn standard that replaces passwords. It provides drop-in auth components and a full API for user management.

Passwords are dead. Passkeys are the future. Hanko makes it easy.

Quick Start

# Self-host with Docker
docker run -p 8000:8000 -p 8001:8001 ghcr.io/nicebook/hanko:latest
Enter fullscreen mode Exit fullscreen mode

Or use Hanko Cloud (free tier: 10,000 MAU).

Drop-in Web Components

npm install @nicebook/hanko-elements
Enter fullscreen mode Exit fullscreen mode
<!-- Login/Register component -->
<hanko-auth api="https://your-hanko.com"></hanko-auth>

<!-- User profile component -->
<hanko-profile api="https://your-hanko.com"></hanko-profile>
Enter fullscreen mode Exit fullscreen mode

Two HTML tags — complete auth flow with passkeys, email codes, and social login.

React Integration

import { useEffect, useState } from "react";
import { register } from "@nicebook/hanko-elements";
import { Hanko } from "@nicebook/hanko-frontend-sdk";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API;
const hanko = new Hanko(hankoApi);

// Auth component
export function LoginPage() {
  useEffect(() => {
    register(hankoApi).catch(console.error);
  }, []);

  return <hanko-auth />;
}

// Check auth status
export function useUser() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    hanko.user.getCurrent()
      .then(setUser)
      .catch(() => setUser(null));
  }, []);

  return user;
}

// Logout
export function LogoutButton() {
  const handleLogout = async () => {
    await hanko.user.logout();
    window.location.href = "/login";
  };
  return <button onClick={handleLogout}>Logout</button>;
}
Enter fullscreen mode Exit fullscreen mode

The Admin API

export HANKO_URL="https://your-hanko.com"
export HANKO_ADMIN_KEY="your-admin-key"

# List users
curl -s "$HANKO_URL/users" \
  -H "Authorization: Bearer $HANKO_ADMIN_KEY" | jq '.[].email'

# Get user
curl -s "$HANKO_URL/users/USER_ID" \
  -H "Authorization: Bearer $HANKO_ADMIN_KEY"

# Delete user
curl -X DELETE "$HANKO_URL/users/USER_ID" \
  -H "Authorization: Bearer $HANKO_ADMIN_KEY"
Enter fullscreen mode Exit fullscreen mode

Frontend SDK

import { Hanko } from "@nicebook/hanko-frontend-sdk";

const hanko = new Hanko("https://your-hanko.com");

// Register with passkey
await hanko.webauthn.register();

// Login with passkey
await hanko.webauthn.login();

// Email login (passcode)
await hanko.passcode.initialize("user@example.com");
await hanko.passcode.finalize("123456"); // Code from email

// Get current user
const user = await hanko.user.getCurrent();
console.log(user.id, user.email);

// Session validation
const session = hanko.session.get();
if (session.isValid) {
  console.log("User is authenticated");
}
Enter fullscreen mode Exit fullscreen mode

Next.js Middleware

// middleware.ts
import { NextRequest, NextResponse } from "next/server";
import { jwtVerify, createRemoteJWKSet } from "jose";

const hankoApi = process.env.HANKO_API_URL;

export async function middleware(req: NextRequest) {
  const token = req.cookies.get("hanko")?.value;
  if (!token) return NextResponse.redirect(new URL("/login", req.url));

  try {
    const JWKS = createRemoteJWKSet(new URL(`${hankoApi}/.well-known/jwks.json`));
    await jwtVerify(token, JWKS);
    return NextResponse.next();
  } catch {
    return NextResponse.redirect(new URL("/login", req.url));
  }
}

export const config = { matcher: ["/dashboard/:path*", "/api/protected/:path*"] };
Enter fullscreen mode Exit fullscreen mode

Why Passkeys?

Metric Passwords Passkeys
Phishing resistant No Yes
User friction High Low (biometric)
Account takeover Common Nearly impossible
Password reuse Major risk N/A
Support by Everyone Apple, Google, Microsoft

Need authentication setup or security consulting?

📧 spinov001@gmail.com
🔧 My tools on Apify Store

Passkeys vs passwords — are you ready to switch? Comment!

Top comments (0)