Integrate Hanko with Next.js
Install @teamhanko/hanko-elements
Once you’ve initialized your Next app, installing hanko-elements provides you with access to the prebuilt components: hanko-auth and hanko-profile.
npm install @teamhanko/hanko-elements
Add the Hanko API URL
Retrieve the API URL from the Hanko console and place it in your .env file.
NEXT_PUBLIC_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io
If you are self-hosting you need to provide the URL of your running Hanko backend.
Add <hanko-auth> component
The <hanko-auth> web component adds a login interface to your app. Begin by importing the register function from @teamhanko/hanko-elements into your Next.js component. Call it with the Hanko API URL as an argument to register <hanko-auth> with the browser’s CustomElementRegistry. Once done, include it in your JSX.
App directory
Pages directory
"use client"
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;
export default function HankoAuth() {
useEffect(() => {
register(hankoApi)
.catch((error) => {
// handle error
});
}, []);
return (
<hanko-auth />
);
}
Now simply import the component you just created (HankoAuth)
App directory
Pages directory
import dynamic from 'next/dynamic';
const HankoAuth = dynamic(() => import('@/components/HankoAuth'), { ssr: false })
export default function LoginPage() {
return (
<HankoAuth />
);
}
Why we use dynamic import in Nextjs? 🤔
We use customEvents and WebAuthn API for passkeys, which are only available on the Client side.
The register function tries to set up the <hanko-auth> element for the browser. However, Next.js creates pages before they’re shown, and it can’t access the browser’s tools during this process. This means our setup might not work.
To fix this, we can use Next.js’s dynamic import to load our component only when it’s displayed on the client-side and skip the pre-creation step.
You’ll need to do this for <hanko-profile> component too.
Define event callbacks
The Hanko client from @teamhanko/hanko-elements lets you “listen” for specific events. It simplifies the process of subscribing to events, such as user logins.
App directory
Pages directory
"use client";
import { useEffect, useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { register, Hanko } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;
export default function HankoAuth() {
const router = useRouter();
const [hanko, setHanko] = useState<Hanko>();
useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoApi))
);
}, []);
const redirectAfterLogin = useCallback(() => {
// successfully logged in, redirect to a page in your application
router.replace("/dashboard");
}, [router]);
useEffect(
() =>
hanko?.onAuthFlowCompleted(() => {
redirectAfterLogin();
}),
[hanko, redirectAfterLogin]
);
useEffect(() => {
register(hankoApi).catch((error) => {
// handle error
});
}, []);
return <hanko-auth />;
}
By now, your sign-up and sign-in features should be working. You should see an interface similar to this 👇

Add <hanko-profile> component
The <hanko-profile> component provides an interface, where users can manage their email addresses and passkeys.
App directory
Pages directory
"use client"
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;
export default function HankoProfile() {
useEffect(() => {
register(hankoApi).catch((error) => {
// handle error
});
}, []);
return <hanko-profile />;
}
It should look like this 👇

Implement logout functionality
You can use @teamhanko/hanko-elements to easily manage user logouts. Below, we make a logout button component that you can use anywhere.
App directory
Pages directory
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Hanko } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL;
export function LogoutBtn() {
const router = useRouter();
const [hanko, setHanko] = useState<Hanko>();
useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoApi ?? ""))
);
}, []);
const logout = async () => {
try {
await hanko?.user.logout();
router.push("/login");
router.refresh();
return;
} catch (error) {
console.error("Error during logout:", error);
}
};
return <button onClick={logout}>Logout</button>;
}
Customize component styles
You can customize the appearance of hanko-auth and hanko-profile components using CSS variables and parts. Refer to our customization guide.
Securing routes with Middleware
To add JWT verification middleware with Hanko in your Next.js application, we’re using jose library. However, you’re free to choose any other suitable library. This middleware will ensure secure access to specific routes, like /dashboard here, by checking for valid JWT. Here we define the Hanko API URL, extract and verify the JWT from cookies, and redirect unauthorized users to the login page.
import { NextResponse, NextRequest } from "next/server";
import { jwtVerify, createRemoteJWKSet } from "jose";
const hankoApiUrl = process.env.NEXT_PUBLIC_HANKO_API_URL;
export async function middleware(req: NextRequest) {
const hanko = req.cookies.get("hanko")?.value;
const JWKS = createRemoteJWKSet(
new URL(`${hankoApiUrl}/.well-known/jwks.json`)
);
try {
const verifiedJWT = await jwtVerify(hanko ?? "", JWKS);
} catch {
return NextResponse.redirect(new URL("/login", req.url));
}
}
export const config = {
matcher: ["/dashboard"],
};
Getting User and Session Data
Client Side
We will create two custom hooks to fetch the current user’s data and session data using @teamhanko/hanko-elements. The useUserData hook leverages the hanko.user.getCurrent() method to retrieve the currently logged-in user’s data.
On the other hand, the useSessionData hook uses the hanko.session.isValid() and hanko.session.get() methods to validate and fetch the current session data.
useUserData
useSessionData
import { useState, useEffect } from "react";
import { Hanko } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL || "";
interface HankoUser {
id: string;
email: string;
loading: boolean;
error: string | null;
}
export function useUserData(): HankoUser {
const [hanko, setHanko] = useState<Hanko>();
const [userState, setUserState] = useState<HankoUser>({
id: "",
email: "",
loading: true,
error: null,
});
useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoApi))
);
}, []);
useEffect(() => {
hanko?.user
.getCurrent()
.then(({ id, email }) => {
setUserState({ id, email, loading: false, error: null });
})
.catch((error) => {
setUserState((prevState) => ({ ...prevState, loading: false, error }));
});
}, [hanko]);
return userState;
}
This is how you can use them according to your needs.
App directory
Pages directory
"use client"
import { useUserData } from "@/hooks/useUserData";
import { useSessionData } from "@/hooks/useSessionData";
const UserData = () => {
const { id, email, loading: userDataLoading, error: userDataError } = useUserData();
const { userID, jwt, isValid, loading: sessionDataLoading, error: sessionDataError } = useSessionData();
if (userDataLoading) {
return <div>Loading...</div>;
}
return (
<div>
<div>User id: {id}</div>
<div>User email: {email}</div>
</div>
);
};
export default UserData;
Server Side
On the server side, you can extract the userID from the JWT. However, if you need to retrieve the complete user details, you’ll have to utilize the Hanko Admin API.
The Hanko Admin API provides detailed information about the status, user management, metrics and more. To get data for a specific user, you’ll need to call the /users/{id} endpoint, where id is the user id previously obtained from the JWT.
The Hanko Admin API is a paid feature and must be enabled explicitly. You also need an API key secret to access the Hanko Admin API. You can generate one under the Settings/API Keys section of your project.
Keep in mind, that the API key secret is only shown once, so make sure to store it in a safe place, like an environmental variable as shown in the following example.
Using JWT
Using Admin API
import { cookies } from "next/headers";
import * as jose from "jose";
export async function userId() {
const token = cookies().get("hanko")?.value;
const payload = jose.decodeJwt(token ?? "");
const userID = payload.sub;
return userID;
}
Try it yourself
Next.js example (App router)
Full source code available on our GitHub
Was this page helpful?