Create a Next.js application
Run the following command to create a new Next.js application :
During the Next.js setup you could pick between two routers, the App Router and the Pages Router .
Ensure you add the correct code and follow the correct directory structure for Router that you picked.
Install @teamhanko/hanko-elements
Once you’ve initialized your NextJS app, installing hanko-elements provides you with access to the prebuilt components: hanko-auth
and hanko-profile
.
Setup your Hanko project
Go to the Hanko console and create a project for this application.
During creation make sure to input the URL you will be developing on as the APP URL
.
(Most likely http://localhost:3000/ ) \
Add your Hanko API URL
Retrieve your API URL from the Hanko console , and paste this in a .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.
Create Hanko components
Create a folder called components
and create two files, HankoAuth.tsx
and HankoProfile.jsx
.
Typescript
To get these elements to work with typescript, currently we must add the types to the project.
To do do this, create a file called custom.d.ts
and place it in your apps root / src folder.
import { HankoAuthElementProps , HankoProfileElementProps , HankoEventsElementProps } from "@teamhanko/hanko-elements" ;
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"hanko-auth" : HankoAuthElementProps ;
"hanko-login" : HankoAuthElementProps ;
"hanko-registration" : HankoAuthElementProps ;
"hanko-profile" : HankoProfileElementProps ;
"hanko-events" : HankoEventsElementProps ;
}
}
}
Hanko Auth
Now lets setup the HankoAuth.tsx
file to create a functioning login page.
Here we subscribe to the onSessionCreated
event , this triggers when a user successfully logs in. You can use these event to perform any desired action. (e.g. redirect to your dashboard).
For more information please refer to the Auth Component Page.
App directory Pages directory "use client" ; //Only for NextJS App Router
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 (() => {
// change this to the page you wish to be redirected to
router . replace ( "/dashboard" );
}, [ router ]);
useEffect (
() =>
hanko ?. onSessionCreated (() => {
// successfully logged in
redirectAfterLogin ();
}),
[ hanko , redirectAfterLogin ]
);
useEffect (() => {
register ( hankoApi ). catch (( error ) => {
// handle error
console . log ( error );
});
}, []);
return < hanko-auth /> ;
}
"use client" ; //Only for NextJS App Router
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 (() => {
// change this to the page you wish to be redirected to
router . replace ( "/dashboard" );
}, [ router ]);
useEffect (
() =>
hanko ?. onSessionCreated (() => {
// successfully logged in
redirectAfterLogin ();
}),
[ hanko , redirectAfterLogin ]
);
useEffect (() => {
register ( hankoApi ). catch (( error ) => {
// handle error
console . log ( 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 (() => setHanko ( new Hanko ( hankoApi )), []);
const redirectAfterLogin = useCallback (() => {
// change this to the page you wish to be redirected to
router . replace ( "/dashboard" );
}, [ router ]);
useEffect (
() =>
hanko ?. onSessionCreated (() => {
//succesfully logged in
redirectAfterLogin ();
}),
[ hanko , redirectAfterLogin ]
);
useEffect (() => {
register ( hankoApi ). catch (( error ) => {
// handle error
console . log ( error )
});
}, []);
return < hanko-auth /> ;
}
Now simply import the component you just created.
App directory Pages directory import HankoAuth from "@/components/HankoAuth" ;
export default function LoginPage () {
return (
< HankoAuth />
);
}
import HankoAuth from "@/components/HankoAuth" ;
export default function LoginPage () {
return (
< 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 👇
Hanko profile
After setting up the HankoAuth let’s set up the HankoProfile.jsx
file to create an interface where users can
manage their Email Addresses
and credentials.
For more information please refer to the Profile Component Page.
App directory Pages directory components/HankoProfile.jsx
"use client" //Only for NextJS App Router
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 /> ;
}
components/HankoProfile.jsx
"use client" //Only for NextJS App Router
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 /> ;
}
components/HankoProfile.jsx
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 /> ;
}
After you created the HankoProfile
component, simply import it into any page.
App directory Pages directory import HankoProfile from "@/components/HankoProfile" ;
export default function Dashboard () {
return (
< div >
< HankoProfile />
</ div >
);
}
import HankoProfile from "@/components/HankoProfile" ;
export default function Dashboard () {
return (
< div >
< HankoProfile />
</ div >
);
}
import HankoProfile from "@/components/HankoProfile" ;
export default function Dashboard () {
return (
< div >
< HankoProfile />
</ div >
);
}
It should look something like this 👇
Implement logout functionality
You can use @teamhanko/hanko-elements
to easily logout users. Here we will make a logout button.
Create LogoutButton.tsx
and insert the code below.
App directory Pages directory components/LogoutButton.tsx
"use client" ; //Only for NextJS App Router
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 ?. logout ();
router . push ( "/" );
router . refresh ();
return ;
} catch ( error ) {
console . error ( "Error during logout:" , error );
}
};
return < button onClick = { logout } > Logout </ button > ;
}
components/LogoutButton.tsx
"use client" ; //Only for NextJS App Router
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 ?. logout ();
router . push ( "/" );
router . refresh ();
return ;
} catch ( error ) {
console . error ( "Error during logout:" , error );
}
};
return < button onClick = { logout } > Logout </ button > ;
}
components/LogoutButton.tsx
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 default function LogoutBtn () {
const router = useRouter ();
const [ hanko , setHanko ] = useState < Hanko >();
useEffect (() => setHanko ( new Hanko ( hankoApi ?? "" )), []);
const logout = async () => {
try {
await hanko ?. logout ();
router . push ( "/" );
router . reload ();
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 verify the session token in your Next.js application, we’re using the session/validate API request . By checking for a valid session token this middleware will ensure secure access to specific routes, like /dashboard
and /protected
.
The middleware extracts and verifies the session token, and redirect unauthorized users back to the home or login page.
For more info on middlewares and where to put the middleware.ts
file,
please refer to NextJS Middleware .
Middleware tends to not always work after creating it, if this is the case try restarting your next app.
import { NextResponse , NextRequest } from "next/server" ;
const hankoApi = process . env . NEXT_PUBLIC_HANKO_API_URL ;
export async function middleware ( req : NextRequest ) {
const token = req . cookies . get ( "hanko" )?. value ;
const validationOptions = {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: `{"session_token":" ${ token } "}`
}
try {
const validationResponse = await fetch (
new URL ( ` ${ hankoApi } /sessions/validate` ), //Hanko session validation
validationOptions
);
if ( ! validationResponse . ok ) {
throw new Error ( 'Session validation failed' );
}
const responseData = await validationResponse . json ();
if ( ! responseData . is_valid ){
throw new Error ( 'Session token not valid' );
}
} catch ( error ) {
console . log ( error )
return NextResponse . redirect ( new URL ( "/" , req . url )); // URL to redirect the user to
}
}
export const config = {
matcher: [ "/dashboard" ],
};
To verify that it works, logout on your app and go to /dashboard
, you should get redirected back.
Getting user data
Client side
Lets use the Hanko SDK to get user data.
Lets update the dashboard
page to log some of the information from the user and session.
App directory Pages directory "use client" //Only for NextJS App Router
import HankoProfile from "../components/HankoProfile" ;
import LogoutButton from "../components/LogoutButton" ;
import { Hanko } from "@teamhanko/hanko-elements" ;
import { useState , useEffect } from "react" ;
const hankoApi = process . env . NEXT_PUBLIC_HANKO_API_URL || "" ;
export default function Dashboard () {
const [ hanko , setHanko ] = useState < Hanko >();
useEffect (() => setHanko ( new Hanko ( hankoApi )), []);
useEffect (() => {
hanko ?. getUser ()
. then (( user ) => {
console . log ( "User profile:" , user ); // Log user Profile
console . log ( user . emails ?.[ 0 ]?. address );
console . log ( user . user_id );
})
}, [ hanko ]);
return (
< div >
< HankoProfile />
< LogoutButton />
</ div >
);
}
"use client" //Only for NextJS App Router
import HankoProfile from "../components/HankoProfile" ;
import LogoutButton from "../components/LogoutButton" ;
import { Hanko } from "@teamhanko/hanko-elements" ;
import { useState , useEffect } from "react" ;
const hankoApi = process . env . NEXT_PUBLIC_HANKO_API_URL || "" ;
export default function Dashboard () {
const [ hanko , setHanko ] = useState < Hanko >();
useEffect (() => setHanko ( new Hanko ( hankoApi )), []);
useEffect (() => {
hanko ?. getUser ()
. then (( user ) => {
console . log ( "User profile:" , user ); // Log user Profile
console . log ( user . emails ?.[ 0 ]?. address );
console . log ( user . user_id );
})
}, [ hanko ]);
return (
< div >
< HankoProfile />
< LogoutButton />
</ div >
);
}
import HankoProfile from "@/components/HankoProfile" ;
import LogoutButton from "@/components/LogoutButton" ;
iimport { Hanko } from "@teamhanko/hanko-elements" ;
import { useState , useEffect } from "react" ;
const hankoApi = process . env . NEXT_PUBLIC_HANKO_API_URL || "" ;
export default function Dashboard () {
const [ hanko , setHanko ] = useState < Hanko >();
useEffect (() => setHanko ( new Hanko ( hankoApi )), []);
useEffect (() => {
hanko ?. getUser ()
. then (( user ) => {
console . log ( "User profile:" , user ); // Log user Profile
console . log ( user . emails ?.[ 0 ]?. address );
console . log ( user . user_id );
})
}, [ hanko ]);
return (
< div >
< HankoProfile />
< LogoutButton />
</ div >
);
}
Server Side
On the server side, you can extract the userID
from the session token, which you can use to fetch the user’s data from the Hanko Public API .
import { cookies } from "next/headers" ;
const hankoApi = process . env . NEXT_PUBLIC_HANKO_API_URL || '' ;
export async function getUserData () {
try {
const token = ( await cookies ()). get ( "hanko" )?. value ;
const validationOptions = {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: `{"session_token":" ${ token } "}`
}
const validationResponse = await fetch ( hankoApi + '/sessions/validate' , validationOptions ); //Get session data
if ( ! validationResponse . ok ) {
throw new Error ( "validation was not succesfull" );
}
const validationData = await validationResponse . json ();
if ( ! validationData . is_valid ){ //Validate session data
throw new Error ( "validation was not succesfull" );
}
const userid = validationData . user_id ; //use user id to request data
const userResponse = await fetch ( hankoApi + '/users/' + userid , validationOptions );
if ( ! userResponse . ok ) {
throw new Error ( "Could not get user data" );
}
const userData = await userResponse . json ();
return userData ;
} catch ( error ) {
console . log ( error )
return null ;
}
return
}
Try it yourself