Create a Remix application

Run the following command to create a new Remix application:

npx create-remix@latest

Install @teamhanko/hanko-elements

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

cd project-name
npm install @teamhanko/hanko-elements

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

Importing and exporting Hanko functions in a .client.ts file

Since the @teamhanko/hanko-elements package is designed to work on the client side, you’ll need to import the Hanko functions into a .client.ts file. This file will then export them, making them available for use throughout your application.

utils/hanko.client.ts
import { register, Hanko } from "@teamhanko/hanko-elements";

export { register, Hanko };

Create Hanko components

Create a folder called components and create two files in it, HankoAuth.tsx and HankoProfile.jsx.

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 { useNavigate, useLoaderData } from "@remix-run/react";
import { Suspense, useCallback, useEffect, useState } from "react";
import { register, type Hanko } from "../utils/hanko.client";

export const loader = () => {
    return { hankoUrl: process.env.HANKO_API_URL }; 
};

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

  const data = useLoaderData<typeof loader>();
  const hankoUrl = data.hankoUrl || '';

  const redirectAfterLogin = useCallback(() => {
      navigate("/dashboard");//Path user gets navigated to once they log in
  }, [navigate]);

  useEffect(() => {
      if (hanko) {
          hanko.onSessionCreated(() => {
              //Succesfully Logged In
              redirectAfterLogin();
          });
      }
  }, [hanko, redirectAfterLogin]);

  useEffect(() => {
      register(hankoUrl)
          .catch((error: Error) => {
              console.error(error.message);
          })
          .then((result) => {
              if (result) {
                  setHanko(result.hanko);
              }
          });
  }, [hankoUrl]);

  return (
      <div className="">
          <Suspense fallback={"Loading..."}>
              <hanko-auth />
          </Suspense>
      </div>
  );
};

export default 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 { useLoaderData } from "@remix-run/react";
import { Suspense, useEffect, } from "react";
import { register } from "../utils/hanko.client";

export const loader = () => {
    return { hankoUrl: process.env.HANKO_API_URL };
};

const HankoProfile = () => {
    const data = useLoaderData<typeof loader>();
    const hankoUrl = data.hankoUrl || '';

    useEffect(() => {
        register(hankoUrl).catch((error) => {
            // handle error
            console.log(error);
          });
    });

    return(
        <Suspense fallback={"Loading..."}>
            <hanko-profile />
        </Suspense>
    )
}
  
export default HankoProfile;

Setup your routes

Let’s setup our routes in the /routes folder.
For our example we will create _index.tsx and dashboard.tsx

routes/_index.tsx
import HankoAuth from "../components/HankoAuth";

export const loader = () => {
  return { hankoUrl: process.env.HANKO_API_URL }; 
};

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

and

routes/dashboard.tsx
import HankoProfile from "../components/HankoProfile";

export const loader = () => {
  return { hankoUrl: process.env.HANKO_API_URL }; 
};

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

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👇

If HankoAuth is not loading please restart the application as it might’ve not loaded the .env correctly the first time.

HankoProfile will only look like the picture while you are logged in.

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 { useLoaderData, useNavigate } from "@remix-run/react";
import { useEffect, useState, } from "react";
import { type Hanko, } from "../utils/hanko.client";


export const loader = () => {
    return { hankoUrl: process.env.HANKO_API_URL };
};

const LogoutButton = () => {

    const data = useLoaderData<typeof loader>();
    const hankoUrl = data.hankoUrl || '';

    const navigate = useNavigate();
    const [hanko, setHanko] = useState<Hanko>();
  
    useEffect(() => {
      import("@teamhanko/hanko-elements").then(({ Hanko }) =>
        setHanko(new Hanko(hankoUrl ?? ""))
      );
    }, []);

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

            navigate("/");//Path the user will be redirected to once logged out
            
            return;
        } catch (error) {
            console.error("Error during logout:", error);
        }
    };

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

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 can validate our cookie token using hankoAPI/sessions/validate. Create a file called auth.server.ts in /services and add this code.

app/services/auth.server.ts
import { parse } from "cookie";

export async function ValidateCurrentSession(request: Request, hankoUrl: string){

    const cookies = parse(request.headers.get("Cookie") || "");
    const token = cookies.hanko;

    const validationOptions = { 
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: `{"session_token":"${token}"}`
    }
    try {
    const response = await fetch(hankoUrl + '/sessions/validate', validationOptions);

    if (!response.ok) throw new Error('Session validation failed');
    
    const verifiedResponse = await response.json();

    return verifiedResponse.is_valid
    
    } catch (error) {
    console.log(error)
    return false;
    }
}

To protect a route using this we can validate it in the loader function.

app/routes/dashboard.tsx
import HankoProfile from "../components/HankoProfile";
import LogoutButton from "../components/LogoutButton";

import { type LoaderFunction, redirect } from "@remix-run/node";
import { ValidateCurrentSession } from "../services/auth.server";

export const loader: LoaderFunction = async ({request}) => {
  const hankoUrl = process.env.HANKO_API_URL || "";
  const validated = await ValidateCurrentSession(request , hankoUrl);

  if(!validated){
    return redirect("/");//Path to redirect to if user is not authenticated
  }

  return { hankoUrl: hankoUrl }; 
};

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

Try it yourself

Remix example

Full source code available on our GitHub