As part of building modern web applications, authentication is a critical aspect, and developers often seek secure, user-friendly, and flexible solutions to handle it. In my recent Next.js project, I chose to integrate Hanko, an authentication provider that allow you integrate auth and passkeys in minutes.
In this article, I’ll walk you through how I used Hanko in my app to implement secure, both password and passwordless authentication for users, cause yes, Hanko can do both :).
Why I Chose Hanko
Hanko is a powerful authentication solution that provides:
- Free 10,000 monthly active users: You can start for free with passkeys, social logins, email passcodes, optional passwords and user management.
- Passwordless authentication: No need to store passwords, reducing security risks.
- Easy to understand an clear documentation: Hanko has probably the best documentation I've ever seen. It guets you started quickly with their framework specific quickstarts so that you can ship fast.
Given the project’s focus on security and ease of use, Hanko was the perfect choice for my app.
What is my App about?
To Don't App is a not so common to do App where instead of adding the tasks you have to do, you add the ones or the habits you have to avoid. The Hanko auth here helps keeping the user data safe as it is persisted in a mongodb database.
It's Live here🎉.
Step-by-Step Integration of Hanko in my App
Here’s how I integrated Hanko into my To Don't app:
1. Setting Up the Next.js Project
I started by creating the Next.js project. If you haven't done so already, you can set up a new Next.js project using the following commands:
npx create-next-app@latest next-todont-app
cd next-todont-app
2. Creating an Account on Hanko
To integrate Hanko, I first needed to set up a Hanko account. I signed up on their platform and created a new project to obtain the necessary API URL for authentication.
1. Sign Up at Hanko.
2. Create a new project and retrieve your API URL.
3. Installing Hanko SDK
I installed the Hanko SDK in my Next.js app to handle the authentication flow.
npm install @teamhanko/hanko-elements
This package provides the client-side tools for registering, authenticating, and managing users.
4. Setting environment variables
I created an .env.local
file in my project root and added my Hanko API URL:
NEXT_PUBLIC_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io
This ensures that the authentication service can securely communicate with Hanko's API.
5. Building the Authentication Pages
Login
To allow users to register and login, I created a page integrating the <hanko-auth>
web component that adds these two interfaces.
First, I imported the register function from @teamhanko/hanko-elements
into my Next.js component. Called it with the Hanko API URL as an argument to register <hanko-auth>
. Once done, I added it in my JSX. Here's a snippet for the component:
components/HankoAuth.tsx
"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 />;
}
Next, I added the component to the login page:
app/login/page.tsx
import HankoAuth from "@/components/HankoAuth";
export default function LoginPage() {
return (
<div className="flex justify-center items-center h-screen">
<HankoAuth />
</div>
);
}
And BOOM 🎉. Just like that, the sign in and sign up features are done. Here is what it looks like 👇
Profile Page
For users to see their account infos, I created a page using the <hanko-profile>
component. This component provides an interface, where users can manage their email addresses and passkeys.
components/HankoProfile.jsx
"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 />;
}
Here is Profile page:
app/profile/page.tsx
import React from "react";
import HankoProfile from "@/components/profile";
export default function ProfilePage() {
return (
<div>
<HankoProfile />
</div>
);
};
Look at this beauty :):
Logout
Now for the Logout. I used @teamhanko/hanko-elements
to easily manage user logouts. Below, I created a logout button component I used on my dashboard.
components/LogoutButton.tsx
"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>;
}
Customization
To make it look seggsy
, I customized the Hanko elements to make the app's UI uniform. To achieve this, i created a dedicated CSS file for the Hanko elements. For further explanation, I really recommend these two articles: Hanko Docs and Esther-Lita post
src/app/hanko.css
:root {
--color: #111827;
--color-shade-1: #222222;
--color-shade-2: #eff5ff;
--brand-color: #000000e8;
--background-color: #ffffff;
--headline1-font-size: 29px;
--headline2-font-size: 16px;
--headline1-margin: 10px 40px;
--border-radius: 6px;
--font-size: 14px;
--item-height: 36px;
--link-color: #111827;
--divider-padding: 0 15px;
}
hanko-auth {
--container-max-width: 640px;
--container-padding: 40px;
}
hanko-profile {
--container-max-width: 550px;
--container-padding: 30px;
}
hanko-profile::part(container) {
border: solid 1px #d8dee3;
border-radius: 6px;
}
hanko-auth::part(button):hover {
background-color: #848587;
border: #848587 solid 1px;
}
hanko-auth::part(headline1),
hanko-profile::part(headline1),
hanko-auth::part(headline2),
hanko-profile::part(headline2) {
color: #1f2328;
line-height: 36px;
text-align: center;
}
hanko-auth::part(form-item) {
min-width: 100%;
}
hanko-auth::part(input),
hanko-profile::part(input) {
background-color: #ffffff;
margin-bottom: 12px;
}
hanko-auth::part(secondary-button) {
border-color: var(--brand-color--);
}
hanko-auth::part(divider) {
margin: 24px 0;
color: var(--brand-color);
}
Here is what it looks like now:
Login:
Profile:
6. Securing API Routes
I ensured that sensitive API routes (like those managing to-do items) were protected by checking whether the user was authenticated before allowing access. This was done by adding middleware that verifies if there's valid JWT and using the jose library. However, you’re free to choose any other suitable library.
middleware.ts
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: ["profile","/dashboard"],
};
This middleware ensures that only authenticated users can access certain API endpoints.
7. Getting User data
To Link th "to-donts" to a specific user, I needed to get the user Id from Hanko. To achieve that, I created a custom hook to fetch the current user’s data using @teamhanko/hanko-elements
. The useUserData hook leverages the hanko.user.getCurrent() method to retrieve the currently logged-in user’s data. I just needed the user Id, but you can get more.
hooks/useUserData.ts
import { useState, useEffect } from "react";
import { Hanko } from "@teamhanko/hanko-elements";
const hankoApi = process.env.NEXT_PUBLIC_HANKO_API_URL || "";
// I just needed the user id here
interface HankoUser {
id: string;
loading: boolean;
error: string | null;
}
export function useUserData(): HankoUser {
const [hanko, setHanko] = useState<Hanko>();
const [userState, setUserState] = useState<HankoUser>({
id: "",
loading: true,
error: null,
});
useEffect(() => setHanko(new Hanko(hankoApi)), []);
useEffect(() => {
hanko?.user
.getCurrent()
.then(({ id }) => {
setUserState({ id, loading: false, error: null });
})
.catch((error) => {
setUserState((prevState) => ({ ...prevState, loading: false, error }));
});
}, [hanko]);
return userState;
}
And here, I used it in my dashboard page:
src/app/dashboard/page.tsx
"use client"
import { useUserData } from "@/hooks/useUserData";
import { useSessionData } from "@/hooks/useSessionData";
const Dashboard = () => {
const { id, loading: userDataLoading, error: userDataError } = useUserData();
if (userDataLoading) {
return <div>Loading...</div>;
}
return (
<div>
<div>User id: {id}</div>
</div>
);
};
export default Dashboard;
8. Deploying on Vercel
After testing the app locally, I deployed it to Vercel for production. Here's how I did it:
- Commit and push the code to a GitHub repository.
- Link the repository to Vercel for automatic deployment.
- Configure the environment variables (Hanko API URL and database related variables) in the Vercel dashboard.
- Deploy the app.
Integrating Hanko in my Next.js app made the authentication process much more secure, user-friendly and fast. With the rise of passwordless authentication and biometrics, Hanko’s approach provides an excellent way to enhance security while offering users a simple and modern login experience.
If you're looking for a fast authentication solution in your Next.js project, I highly recommend giving Hanko a try! Try Hanko now.
Top comments (2)
Aren't you getting CustomEvent error?
Can you tell me where you are getting this error from so I can help you?