DEV Community

Alex Spinov
Alex Spinov

Posted on

Hanko Has a Free API — Heres How to Add Passkey Authentication in 5 Minutes

Hanko is an open-source auth solution focused on passkeys and WebAuthn. Drop in a web component, get passwordless auth — no complex setup.

Why Hanko?

  • Passkeys-first: WebAuthn/FIDO2 native
  • Web component: Add <hanko-auth> to your HTML
  • Passwordless: Email codes + passkeys
  • Self-hosted: Full control
  • OIDC/OAuth: Social login support
  • Free tier: 10K MAUs on cloud

Self-Host

docker run -p 8000:8000 -p 8001:8001 \
  -e "PUBLIC_URL=http://localhost:8000" \
  -e "ADMIN_URL=http://localhost:8001" \
  ghcr.io/teamhanko/hanko:latest
Enter fullscreen mode Exit fullscreen mode

Add to Your Frontend

<script type="module" src="https://cdn.jsdelivr.net/npm/@aspect-build/hanko-elements/dist/elements.js"></script>

<hanko-auth api="http://localhost:8000"></hanko-auth>
Enter fullscreen mode Exit fullscreen mode

That's it. Users get email code + passkey registration.

React Integration

import { Hanko } from '@teamhanko/hanko-elements';
import { useEffect, useState } from 'react';

const hankoApi = 'http://localhost:8000';

function LoginPage() {
  useEffect(() => {
    import('@teamhanko/hanko-elements').then(({ register }) =>
      register(hankoApi)
    );
  }, []);

  return <hanko-auth />;
}

function useHanko() {
  const [hanko] = useState(() => new Hanko(hankoApi));
  return hanko;
}

function Profile() {
  const hanko = useHanko();
  const [user, setUser] = useState(null);

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

  return <div>Welcome, {user?.email}</div>;
}
Enter fullscreen mode Exit fullscreen mode

API: Verify Session

// Backend middleware
import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('http://localhost:8000/.well-known/jwks.json')
);

async function authenticate(req, res, next) {
  const token = req.cookies.hanko;
  if (!token) return res.status(401).json({ error: 'No session' });

  const { payload } = await jwtVerify(token, JWKS);
  req.userId = payload.sub;
  next();
}
Enter fullscreen mode Exit fullscreen mode

API: List Users

curl http://localhost:8001/users \
  -H 'Authorization: Bearer ADMIN_TOKEN'
Enter fullscreen mode Exit fullscreen mode

API: Create User

curl -X POST http://localhost:8001/users \
  -H 'Content-Type: application/json' \
  -d '{"emails": [{"address": "user@example.com", "is_primary": true}]}'
Enter fullscreen mode Exit fullscreen mode

Next.js Integration

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

const JWKS = createRemoteJWKSet(new URL('http://localhost:8000/.well-known/jwks.json'));

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

  try {
    await jwtVerify(token, JWKS);
    return NextResponse.next();
  } catch {
    return NextResponse.redirect(new URL('/login', req.url));
  }
}

export const config = { matcher: ['/dashboard/:path*'] };
Enter fullscreen mode Exit fullscreen mode

Real-World Use Case

A B2C app replaced their email/password auth with Hanko passkeys. Account takeover incidents dropped to zero — passkeys can't be phished. Sign-up conversion improved 25% because users no longer abandon forms at 'create a password'.


Need to automate data collection? Check out my Apify actors for ready-made scrapers, or email spinov001@gmail.com for custom solutions.

Top comments (0)