Here you can see how to integrate auth using hanko .
Hanko : Hanko is a lightweight, open source user authentication solution to quickly setup in your project ( trust me i tried it is very easy to setup. )
In this post, i will show how I implemented authentication using hanko in a react frontend and express backend .
I made a Text summarizer ai .
To see my full code you can go to my github :
frontend:"https://github.com/s2ahil/Hanko-auth-frontend-"
backend :" https://github.com/s2ahil/Hanko-auth-backend"
I will be showing you the important codes for authentication :
*Already docs are available from Hanko *:
https://docs.hanko.io/quickstarts/frontend/react
React codes:
Here is the login code :
import React from 'react'
import { register, Hanko } from "@teamhanko/hanko-elements";
import { useNavigate } from "react-router-dom";
import { useEffect, useMemo, useCallback } from 'react';
const hankoApi = import.meta.env.REACT_APP_HANKO_API
console.log(hankoApi)
export const Login = () => {
const navigate = useNavigate();
const hanko = useMemo(() => new Hanko(hankoApi), []);
const redirectAfterLogin = useCallback(() => {
navigate("/MainPage");
}, [navigate]);
useEffect(
() =>
hanko.onAuthFlowCompleted(() => {
redirectAfterLogin();
}),
[hanko, redirectAfterLogin]
);
useEffect(() => {
register(hankoApi).catch((error) => {
console.log(error)
// handle error
});
if (hanko.session.isValid() === true) {
navigate('/MainPage')
}
}, []);
return (
<div className='flex min-h-screen justify-center items-center'>
<div className='w-[400px] p-10 border rounded-xl shadow-md '>
<hanko-auth />
</div>
{/* <hanko-auth api={hankoApi} /> */}
</div>
)
}
Here is logout code :
import React,{ useState,useEffect} from 'react'
import { useNavigate } from "react-router-dom";
import { Hanko } from "@teamhanko/hanko-elements";
const hankoApi = import.meta.env.REACT_APP_HANKO_API
export const Logout = () => {
const navigate = useNavigate();
const [hanko, setHanko] = useState(null)
useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoApi ?? ""))
);
}, []);
const logout = async () => {
try {
await hanko?.user.logout();
navigate("/");
} catch (error) {
console.error("Error during logout:", error);
}
};
return (<>
<button className=" px-4 py-2 mt-2 bg-blue-500 text-white rounded hover:bg-green-600" onClick={async()=>await logout()}>logout</button>
</>
)
}
Here is the profile code :
import React, { useEffect, useState } from 'react';
import { register } from "@teamhanko/hanko-elements";
import { useNavigate } from "react-router-dom";
import { Hanko } from "@teamhanko/hanko-elements";
const hankoApi = import.meta.env.REACT_APP_HANKO_API;
const hanko = new Hanko(import.meta.env.REACT_APP_HANKO_API);
export const Profile = () => {
const navigate = useNavigate();
const [showProfilePage, setShowProfile] = useState(false);
useEffect(() => {
async function fetchData() {
await register(hankoApi);
}
fetchData();
}, []);
return (<>
<button
className="px-4 py-2 mb-2 bg-blue-500 text-white rounded hover:bg-green-600"
onClick={() => setShowProfile(!showProfilePage)}
>
Profile Setting
</button>
{
showProfilePage && (
<div className='w-[400px] p-3 border rounded-xl shadow-md bg-[#FFFFFF] '>
<hanko-profile />
</div>
)
}
</>);
}
Here is the final main page :
import React, { useEffect, useState } from 'react';
import { Profile } from './Profile';
import { Logout } from './Logout';
import axios from 'axios';
import { TypeAnimation } from 'react-type-animation';
import { Hanko } from "@teamhanko/hanko-elements";
import { useNavigate } from "react-router-dom";
export const MainPage = () => {
const navigate = useNavigate();
const [text, setText] = useState('');
const [showMainPage, setShowMainPage] = useState(false);
const [response, setResponse] = useState('');
const [error, setError] = useState(null); // State variable for error message
const [loading, setLoading] = useState(false);
const hanko = new Hanko(import.meta.env.REACT_APP_HANKO_API);
const send = () => {
setLoading(true);
setError(null); // Clear previous error message, if any
console.log()
axios.post("http://localhost:3000/summarize", { text_to_summarize: text }, { withCredentials: true })
.then(res => {
console.log('response', JSON.stringify(res.data));
setResponse(res.data);
})
.catch(error => {
console.error('Error:', error);
setError("An error occurred. Please try again."); // Set the error message
})
.finally(() => {
setLoading(false);
});
}
useEffect(()=>{
if (hanko.session.isValid() === false) {
navigate('/login')
}
})
return (
<div className="p-8 bg-gray-100">
<div className='relative space-x-10'>
<Profile />
<Logout />
</div>
<center><div className='m-4 mb-8 text-xl font-bold leading-none tracking-tight text-gray-900 md:text-2xl lg:text-3xl dark:text-black'>AI TEXT SUMMARIZER</div></center>
<div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div className='flex flex-col'>
<textarea
className="mt-5 p-2 border border-gray-300 rounded min-h-[20rem] w-full"
placeholder="Enter text to summarize"
onChange={(e) => setText(e.target.value)}
value={text}
/>
<div className='max-w-[20rem] mx-auto'>
<button
className={`px-5 py-3 mt-2 w-full bg-blue-500 text-white rounded-full hover:bg-green-600 ${loading ? 'cursor-not-allowed' : ''}`}
onClick={send}
disabled={loading}
>
{loading ? 'Loading...' : 'Send'}
</button>
</div>
</div>
<div>
{error ? ( // Check for error message
<div className="mt-4 text-red-800">
<strong>Error:</strong>
<p>{error}</p>
</div>
) : response === '' ? (
<div className='mt-5'>
<div className='flex flex-col'>
<p className="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-4xl lg:text-5xl dark:text-black">
{loading ? 'Loading...' : <> <TypeAnimation
sequence={[
// Same substring at the start will only be typed out once, initially
'Try typing something to see the summarized text',
2000, // wait 1s before replacing "Mice" with "Hamsters"
'Our AI can summarize anything',
2000,
]}
wrapper="span"
speed={50}
repeat={Infinity}
/></> }
</p>
{loading ? (
<div className="w-20 h-20 mx-auto">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
</div>
) : (
<div className=''>
<iframe
height="250"
width="250"
src="https://giphy.com/embed/26AHONQ79FdWZhAI0"
className="giphy-embed "
></iframe>
</div>
)}
</div>
</div>
) : (
<div className="mt-4 text-gray-800">
<strong>Summary:</strong>
<p>{response}</p>
</div>
)}
</div>
</div>
</div>
</div>
);
};
Express.js codes :
Here is backend code :"
const express = require('express');
const bodyParser = require('body-parser');
const cors = require("cors");
const cookieParser = require("cookie-parser");
const jose = require("jose"); // Import the jose library
const dotenv = require("dotenv");
const summarizeText = require('./summarize')
dotenv.config()
const app = express();
app.use(cors({ credentials: true, origin: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }))
app.use(cookieParser());
console.log(process.env.HANKO_API_URL)
async function Middleware(req, res, next) {
const token = req.cookies.hanko;
console.log("token", token)
if (!token) {
return res.status(401).json({ error: "Unauthorized" });
}
const hankoApi = process.env.HANKO_API_URL
const JWKS = jose.createRemoteJWKSet(
new URL(`${hankoApi}/.well-known/jwks.json`)
);
console.log(JWKS)
try {
// Verify the token using the JWKS
console.log("aya 1")
const verifiedJWT = await jose.jwtVerify(token, JWKS);
console.log("aya 2")
// console.log(verifiedJWT)
req.auth = verifiedJWT; // Attach the authentication data to the request object
next();
} catch (error) {
return res.status(401).json({ error: "Unauthorized" });
}
}
app.get("/protected", (req, res) => {
// Only users with a valid Hanko token will reach this route
if (req.auth) {
console.log(req.auth)
res.sendStatus(200);
} else {
res.status(401).json({ error: "Unauthorized" });
}
});
app.post("/summarize", Middleware, (req, res) => {
if (req.auth) {
const text = req.body.text_to_summarize;
console.log(text)
summarizeText(text)
.then(response => {
res.send(response); // Send the summary text as a response to the client
})
.catch(error => {
console.log(error.message);
});
}else{
res.status(401).json({ error: "Unauthorized" });
}
// get the text_to_summarize property from the request body
});
app.get('/', (req, res) => {
res.send("hello");
})
app.listen(3000, () => {
console.log("Server started at 3000");
});
Note : somehow while deploying req.cookies was not showing cookies from browser , but it was working fine in local host . you can check from your side also .
Finally, i deployed my frontend to : Vercel
Express backend to : render.com
Full Live Working project Link :"https://hanko-auth-frontend.vercel.app/"
Top comments (1)
💛🌴