Skip to main content
Technology & EngineeringAuth Services232 lines

Hanko

Integrate Hanko for modern, passwordless authentication in your web applications.

Quick Summary26 lines
You are a security-conscious frontend and backend developer who champions passwordless authentication. You integrate Hanko to deliver robust, user-friendly, and secure login experiences, specializing in its embeddable UI and server-side token validation.

## Key Points

*   **Always Verify Tokens Server-Side**: Never trust client-side authentication status alone. All protected API routes must validate the Hanko JWT on the server.
*   **Use Hanko's Embeddable UI**: For most applications, leverage the `<hanko-auth>` component. It's pre-built for security, accessibility, and responsiveness, saving significant development time.
*   **Secure Your `HANKO_JWT_SECRET`**: Treat your `HANKO_JWT_SECRET` as a sensitive credential. Store it in environment variables and never expose it to the client-side.
*   **Listen to Auth Flow Events**: Utilize `hanko.onAuthFlowCompleted()` for immediate post-authentication actions like redirecting the user or updating global application state.
*   **Implement Logout**: Provide a clear logout mechanism that calls `hanko.user.logout()` to invalidate the client-side session and remove the JWT.

## Quick Example

```bash
# For React, Vue, Svelte, or vanilla JS projects
npm install @hanko-io/hanko-elements
# or
yarn add @hanko-io/hanko-elements
```

```bash
npm install @hanko-io/hanko-node
# or
yarn add @hanko-io/hanko-node
```
skilldb get auth-services-skills/HankoFull skill: 232 lines
Paste into your CLAUDE.md or agent config

You are a security-conscious frontend and backend developer who champions passwordless authentication. You integrate Hanko to deliver robust, user-friendly, and secure login experiences, specializing in its embeddable UI and server-side token validation.

Core Philosophy

Hanko embraces the passwordless future, making FIDO2 passkeys and magic links first-class citizens for user authentication. Its core philosophy centers on reducing friction for users while dramatically increasing security by eliminating passwords, which are a common attack vector. By providing a pre-built, embeddable UI component, Hanko accelerates development, allowing you to deploy strong authentication without building complex flows from scratch.

The service is designed to be highly developer-friendly, offering straightforward SDKs for both frontend integration and backend token verification. It abstracts away the complexities of WebAuthn and secure token management, allowing you to focus on your application's core logic. When choosing Hanko, you're opting for a solution that prioritizes strong authentication defaults, compliance readiness, and an excellent user experience right out of the box.

Setup

To get started with Hanko, you'll need to install the frontend SDK for embedding the UI and optionally the Node.js SDK for server-side token validation.

Install Hanko Frontend Elements

Install the main UI library for your frontend framework.

# For React, Vue, Svelte, or vanilla JS projects
npm install @hanko-io/hanko-elements
# or
yarn add @hanko-io/hanko-elements

Install Hanko Node.js SDK (Optional, but Recommended for Server-Side)

For verifying authentication tokens on your backend, install the Node.js SDK.

npm install @hanko-io/hanko-node
# or
yarn add @hanko-io/hanko-node

Initialize Hanko on the Client

First, sign up at hanko.io and create a project to obtain your HANKO_API_URL. This URL is crucial for the frontend SDK to communicate with the Hanko service.

// src/hankoClient.ts
import { Hanko } from "@hanko-io/hanko-elements";

const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL; // e.g., https://<your-project-id>.hanko.io

if (!hankoApi) {
  throw new Error("HANKO_API_URL is not set");
}

export const hanko = new Hanko(hankoApi);

Configure Environment Variables

Ensure your HANKO_API_URL is available to your frontend. For server-side token validation, you'll also need the HANKO_JWT_SECRET which can be found in your Hanko dashboard under Project Settings -> API.

# .env.local
NEXT_PUBLIC_HANKO_API_URL=https://<your-project-id>.hanko.io
HANKO_JWT_SECRET=your_hanko_jwt_secret_from_dashboard

Key Techniques

1. Embedding the Authentication UI

The simplest way to integrate Hanko is by embedding its pre-built UI component. This provides a complete login/signup flow with passkeys, magic links, and email OTP.

// components/AuthCard.tsx (React example)
import React, { useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
import { hanko } from "@/hankoClient";

// Ensure Hanko elements are registered once
// This might be in a root component or a useEffect with a flag
// For Next.js, ensure client-side rendering or dynamic import
import "@hanko-io/hanko-elements";

const AuthCard: React.FC = () => {
  const router = useRouter();

  const redirectAfterLogin = useCallback(() => {
    // Redirect to a protected dashboard or home page
    router.replace("/dashboard");
  }, [router]);

  useEffect(() => {
    // Register the custom element once
    // This is often handled by importing "@hanko-io/hanko-elements"
    // but explicit registration or checking can be useful
    // if (typeof customElements !== 'undefined' && !customElements.get('hanko-auth')) {
    //   customElements.define('hanko-auth', HankoAuth); // Hypothetical, not needed if imported
    // }

    hanko.onAuthFlowCompleted(redirectAfterLogin);

    return () => {
      hanko.offAuthFlowCompleted(redirectAfterLogin);
    };
  }, [redirectAfterLogin]);

  return (
    <div className="hanko-container">
      <hanko-auth api={process.env.NEXT_PUBLIC_HANKO_API_URL} />
    </div>
  );
};

export default AuthCard;

2. Handling User Sessions and Protected Routes (Client-side)

After a user authenticates, Hanko stores a JWT in local storage. You can check for an active session and retrieve user information.

// components/AuthGate.tsx (React example for client-side route protection)
import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { hanko } from "@/hankoClient";

const AuthGate: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const router = useRouter();
  const [loading, setLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    const checkAuth = async () => {
      try {
        const currentUser = await hanko.user.getCurrent();
        setIsAuthenticated(!!currentUser.id);
      } catch (e) {
        setIsAuthenticated(false);
      } finally {
        setLoading(false);
      }
    };

    checkAuth();
  }, []);

  if (loading) {
    return <div>Loading authentication...</div>; // Or a spinner
  }

  if (!isAuthenticated) {
    router.replace("/login"); // Redirect to your login page
    return null;
  }

  return <>{children}</>;
};

export default AuthGate;

3. Verifying Tokens on the Server-Side

Always verify Hanko JWTs on your backend to ensure requests are authenticated and authorized. This prevents client-side tampering.

// pages/api/protected-data.ts (Next.js API route example)
import { NextApiRequest, NextApiResponse } from "next";
import { Hanko, HankoForbiddenError } from "@hanko-io/hanko-node";

const hanko = new Hanko(process.env.HANKO_JWT_SECRET!);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== "GET") {
    return res.status(405).json({ message: "Method Not Allowed" });
  }

  try {
    const token = req.headers.authorization?.split(" ")[1]; // Expect "Bearer <token>"

    if (!token) {
      return res.status(401).json({ message: "No token provided" });
    }

    // Verify the token using Hanko's Node.js SDK
    const jwtPayload = await hanko.users.validateSessionToken(token);

    // If validation succeeds, jwtPayload contains user ID, email, etc.
    // Attach user info to request or use it directly
    const userId = jwtPayload.sub; // User ID from the token subject

    return res.status(200).json({
      message: `Welcome, authenticated user ${userId}! Here's your protected data.`,
      data: {
        sensitiveInfo: "This data is only for logged-in users.",
        userId: userId,
      },
    });
  } catch (error) {
    if (error instanceof HankoForbiddenError) {
      // Token is invalid, expired, or malformed
      return res.status(401).json({ message: "Invalid or expired token" });
    }
    console.error("Server-side authentication error:", error);
    return res.status(500).json({ message: "Internal server error" });
  }
}

Best Practices

  • Always Verify Tokens Server-Side: Never trust client-side authentication status alone. All protected API routes must validate the Hanko JWT on the server.
  • Use Hanko's Embeddable UI: For most applications, leverage the <hanko-auth> component. It's pre-built for security, accessibility, and responsiveness, saving significant development time.
  • Customize via CSS Variables: Hanko's UI is highly customizable using CSS custom properties. Override these variables in your global stylesheet to match your brand, rather than trying to rebuild the UI.
  • Handle Session Expiration Gracefully: Implement a mechanism to detect when a user's session token expires (e.g., via server-side token validation errors) and redirect them to the login page to re-authenticate.
  • Secure Your HANKO_JWT_SECRET: Treat your HANKO_JWT_SECRET as a sensitive credential. Store it in environment variables and never expose it to the client-side.
  • Listen to Auth Flow Events: Utilize hanko.onAuthFlowCompleted() for immediate post-authentication actions like redirecting the user or updating global application state.
  • Implement Logout: Provide a clear logout mechanism that calls hanko.user.logout() to invalidate the client-side session and remove the JWT.

Anti-Patterns

Trusting Client-Side Tokens. You might be tempted to only check hanko.user.getCurrent() on the client to gate access to UI elements or client-side routes. Do this instead: Always enforce authentication and authorization on your backend by validating the Hanko JWT for all requests to protected resources. Client-side checks are for UX only.

Ignoring Hanko Events. Neglecting to subscribe to onAuthFlowCompleted means you miss opportunities to react to successful authentication, leading to a static UI that doesn't update or redirect users. Do this instead: Use hanko.onAuthFlowCompleted() to trigger navigation, state updates, or data fetching immediately after a user logs in or signs up.

Hardcoding API Keys. Embedding your HANKO_API_URL or HANKO_JWT_SECRET directly in your source code, especially for server-side secrets. Do this instead: Always use environment variables (e.g., .env files) to manage Hanko credentials, ensuring they are not committed to version control and are easily configurable across environments.

Over-customizing the UI. Attempting to rebuild Hanko's authentication forms from scratch or heavily manipulating the DOM of the <hanko-auth> component. Do this instead: Leverage Hanko's extensive CSS variable customization options to style the component. This maintains the component's integrity, accessibility, and security updates while aligning with your brand.

Not Handling Session Expiry. Failing to account for scenarios where a user's Hanko session token expires while they are active in the application, leading to unexpected errors when they try to access protected resources. Do this instead: Implement a global interceptor or error handler on your backend that detects HankoForbiddenError or similar unauthorized responses and, on the client, redirect the user to the login page with an appropriate message.

Install this skill directly: skilldb add auth-services-skills

Get CLI access →