Create a React application

To setup our React Frontend we will use vite as a build tool.

Run the following command to create a new Vite React application:

npm create vite@latest project-name -- --template react-ts

Install @teamhanko/hanko-elements

Once you’ve initialized your React app, installing hanko-elements provides you with access to the prebuilt components: hanko-auth and hanko-profile.

We will also install the react-router-dom, this will allow us to navigate trough pages / routes.

cd project-name
npm install @teamhanko/hanko-elements react-router-dom

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:5173/)

Add the Hanko API URL

Retrieve the API URL from the Hanko console and place it in your .env file.

.env
VITE_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 in it, HankoAuth.tsx and HankoProfile.tsx.

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.

src/custom.d.ts
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.

components/HankoAuth.tsx
import { useEffect, useCallback, useMemo } from "react";
import { useNavigate } from 'react-router-dom';
import { register, Hanko } from "@teamhanko/hanko-elements";

const hankoApi = import.meta.env.VITE_HANKO_API_URL;
 
export default function HankoAuth() {
  const navigate = useNavigate();
 
  const hanko = useMemo(() => new Hanko(hankoApi), []);
 
  const redirectAfterLogin = useCallback(() => {
    // redirect to a page in your application
    navigate("/dashboard");
  }, [navigate]);

  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.

pages/loginPage.tsx
import HankoAuth from "../components/HankoAuth";

export default function LoginPage() {
  return (
      <HankoAuth/>
  );
}

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.

components/HankoProfile.tsx
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";

const hankoApi = import.meta.env.VITE_HANKO_API_URL;

export default function HankoProfile() {
  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
      console.log(error)
    });
  }, []);

  return <hanko-profile />;
}

After you created the HankoProfile component, simply import it into any page.

pages/dashboardPage.tsx
import HankoProfile from "../components/HankoProfile";

export default function Dashboard() {
  return (
    <div>
      <HankoProfile/>
    </div>
  );
}

Setup your routes

After you created the LoginPage.tsx and DashboardPage.tsx you are able to import them into your react App.tsx. We will use react-router-dom to setup the routes of your app.

App.tsx
import './App.css'
import { Route, createBrowserRouter, createRoutesFromElements, RouterProvider } from 'react-router-dom';

import LoginPage from './pages/loginPage';
import Dashboard from './pages/dashboardPage';


const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/">
      {/* index route with Login page*/}
      <Route index element={<LoginPage />} /> 
       {/* dashboard route with Dashboard page*/}
      <Route path="dashboard" element={<Dashboard />} />
    </Route>
  )
)

export default function App(){
  return (
    <RouterProvider router={router} />
  )
}

By now you should be able to go to / to see the <HankoAuth>, and to /dashboard to see the <HankoProfile>.

They 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.

components/LogoutButton.tsx
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Hanko } from "@teamhanko/hanko-elements";

const hankoApi = import.meta.env.VITE_HANKO_API_URL;

function LogoutBtn() {
  const navigate = useNavigate();
  const [hanko, setHanko] = useState<Hanko>();

  useEffect(() => {
    import("@teamhanko/hanko-elements").then(({ Hanko }) =>
      setHanko(new Hanko(hankoApi ?? ""))
    );
  }, []);

  const logout = async () => {
    try {
      await hanko?.logout();

      navigate("/"); //Path to naviage to once the user logs out.

    } catch (error) {
      console.error("Error during logout:", error);
    }
  };

  return <button onClick={logout}>Logout</button>;
}

export default LogoutBtn;

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

To secure our routes we should validate the session token at the backend. Please refer to our backend guides.

Lets set up a Private route to do this for us. Create a new React component at components/PrivateRoute.tsx.

If the backend couldn’t validate our token we get navigated back to /. Otherwise if the validation was successfull it will return the children inside the private route.

components/PrivateRoute.tsx
import { ReactNode, useEffect, useState } from 'react';
import { Navigate, useLocation } from 'react-router-dom';

export default function PrivateRoute({ children }: { children: ReactNode }) {
    const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
    const location = useLocation();
  
    useEffect(() => {
      fetch('http://localhost:5001/validate', {// Change this to the validation url of your running backend
        credentials: 'include', // This is required to include the cookie in the request
      })
        .then((res) => {
          setIsAuthenticated(res.ok);
        })
        .catch(() => {
          setIsAuthenticated(false);
        });
    }, []);
  
    if (isAuthenticated === null) {
      return null; // Or a loading spinner
    }

    if(isAuthenticated){ return <>{children}</> }

    //Url to naviage user to if they arent authenticated
    return <Navigate to="/" replace state={{ from: location }} />
  }

Lets import this PrivateRoute.tsx to your App.tsx file.
To use the private route wrap your Dashboard in the Private Route;

App.tsx
import './App.css'
import { Route, createBrowserRouter, createRoutesFromElements, RouterProvider } from 'react-router-dom';

import LoginPage from './pages/loginPage';
import Dashboard from './pages/dashboardPage';

import PrivateRoute from './components/PrivateRoute';

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/">
      {/* index route with Login page*/}
      <Route index element={ <LoginPage />} /> 
       {/* secured dashboard route with Dashboard page*/}
      <Route path="dashboard" element={<PrivateRoute> <Dashboard /> </PrivateRoute>} />
    </Route>
  )
)

export default function App(){
  return (
    <RouterProvider router={router} />
  )
}

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.

pages/dashboardPage.tsx
import { useState, useEffect, useMemo } from "react";
import { Hanko } from "@teamhanko/hanko-elements";

import HankoProfile from "../components/HankoProfile";

const hankoApi = process.env.REACT_APP_HANKO_API_URL;

export default function Dashboard() {
  const hanko = useMemo(() => new Hanko(hankoApi), []);

  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/>
    </div>
  );
}

Try it yourself

React example (Vite)

It uses Express.js for the backend, full source code available on our GitHub.