Integrate Hanko with Next.js
Learn how to quickly add authentication and user profile in your Next.js app using Hanko.
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
.
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.
The Hanko client from @teamhanko/hanko-elements
allows you to subscribe to specific events. For instance, the onSessionCreated
event here triggers when a user successfully logs in. You can use this event to perform any desired action.
"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(() => setHanko(new Hanko(hankoApi)), []);
const redirectAfterLogin = useCallback(() => {
// successfully logged in, redirect to a page in your application
router.replace("/dashboard");
}, [router]);
useEffect(
() =>
hanko?.onSessionCreated(() => {
redirectAfterLogin();
}),
[hanko, redirectAfterLogin]
);
useEffect(() => {
register(hankoApi).catch((error) => {
// handle error
});
}, []);
return <hanko-auth />;
}
Now simply import the component you just created (HankoAuth)
import HankoAuth from "@/components/HankoAuth";
export default function LoginPage() {
return (
<HankoAuth />
);
}
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.
"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.
"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 default function LogoutBtn() {
const router = useRouter();
const [hanko, setHanko] = useState<Hanko>();
useEffect(() => 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.
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(() => 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.
"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, which you can use to fetch the user’s data from the Hanko Public API.
import { cookies } from "next/headers";
import * as jose from "jose";
const hankoApiUrl = process.env.NEXT_PUBLIC_HANKO_API_URL;
export async function getUserData() {
const token = cookies().get("hanko")?.value;
const payload = jose.decodeJwt(token ?? "");
const userID = payload.sub;
const response = await fetch(`${hankoApiUrl}/users/${userID}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
return null;
}
const userData = await response.json();
return userData;
}
Try it yourself
Was this page helpful?