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
.
- npm
- Yarn
npm install @teamhanko/hanko-elements
yarn add @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
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 />;
}
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 HankoAuth from "@/components/HankoAuth";
export default function LoginPage() {
return (
<HankoAuth />
);
}
import dynamic from "next/dynamic";
export default function LoginPage() {
const HankoAuth = dynamic(
() => import('@/components/HankoAuth'),
{ ssr: false },
)
return (
<Suspense fallback={"Loading ..."}>
<HankoAuth/>
</Suspense>
)
}
Why do we use dynamic import in the Nextjs pages directory? 🤔
The register function tries to set up the <hanko-auth>
element for the browser. But, Next.js pre-renders pages before displaying them. Since it doesn't have access to the browser's window
object during this pre-render phase, the registration of the custom element would fail.
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.
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 />;
}
import { useEffect, useCallback, useState } from "react";
import { useRouter } from "next/router";
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 />;
}
Add <hanko-profile>
component
The <hanko-profile>
component offers an interface for managing 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 />;
}
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 />;
}
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>;
}
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
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"],
};