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
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>
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>;
}
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();
}
API: List Users
curl http://localhost:8001/users \
-H 'Authorization: Bearer ADMIN_TOKEN'
API: Create User
curl -X POST http://localhost:8001/users \
-H 'Content-Type: application/json' \
-d '{"emails": [{"address": "user@example.com", "is_primary": true}]}'
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*'] };
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)