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.
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(() => setHanko(new Hanko(hankoApi)), []);
const redirectAfterLogin = useCallback(() => {
router. replace ( "/dashboard" ) ;
} , [router]);
useEffect(
() =>
hanko?.onSessionCreated(() => {
redirectAfterLogin ( ) ;
} ),
[hanko, redirectAfterLogin]
);
useEffect(() => {
register ( hankoApi) . catch ( ( 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 />
) ;
}
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 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 ) => {
} ) ;
} , [ ] ) ;
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 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> ;
}
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 ( ( ) => 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, 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