Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashutosh-Bhadauriya committed Apr 23, 2024
0 parents commit c943a9b
Show file tree
Hide file tree
Showing 45 changed files with 5,085 additions and 0 deletions.
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Logs
Logs/
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Node modules and .env in any directory
**/node_modules/
**/.env

# Distribution directories
dist/
dist-ssr/

# Local files
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea/
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2012-2024 Hanko GmbH

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
125 changes: 125 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Integrating Passkey Authentication in a Web App

This repository demonstrates how to add passkey login functionality to your Web app using the Hanko Passkey API. Passkey authentication is a secure and user-friendly alternative to traditional password-based authentication, providing a seamless login experience for users.

![Passkey demo](/passkey.gif)

## Prerequisites

Before you begin, ensure you have the following:

- Node.js installed (version 20.0.0 or later)
- Hanko Passkey API key and tenant ID from [Hanko Cloud](https://cloud.hanko.io/)

> **Note:**
> You'll need to create a Passkey Project on Hanko Cloud with the App URL `http://localhost:5173`. See our docs to learn how to setup a [passkey project](https://docs.hanko.io/passkey-api/setup-passkey-project).
## Getting started

1. Clone the repository

```bash
git clone https://github.com/teamhanko/passkeys-web-app
```

2. Set up environment variables

Create a `.env` file in the `express-backend` directory and add the following environment variables:

```sh
PASSKEYS_API_KEY=your-hanko-passkey-api-key
PASSKEYS_TENANT_ID=your-hanko-passkey-tenant-id
```

Replace `your-hanko-passkey-api-key` and `your-hanko-passkey-tenant-id` with your actual Hanko Passkey API key and tenant ID.

#### Frontend

1. Navigate to the frontend directory:

```bash
cd frontend
```

2. Install the frontend dependencies using your preferred package manager (e.g., `npm`, `pnpm`, `yarn`, or `bun`). For this project, we've used `pnpm`:

```bash
pnpm install
```

3. Start the frontend development server:

```bash
pnpm dev
```

#### Backend

1. Navigate to the backend directory:

```bash
cd backend
```

2. Install the backend dependencies:

```bash
pnpm install
```

3. Start the backend server:

```bash
pnpm dev
```

## Usage

1. Start the application:

* Ensure that both the frontend and backend servers are running.

* Access the application by navigating to `http://localhost:5173` in your web browser.

2. Log in with a pre-configured user: Navigate to login page, login with one of the pre-configured users.

```json
{
"id": "b3fbbdbd-6bb5-4558-9055-3b54a9469629",
"email": "[email protected]",
"password": "password123",
},
{
"id": "22c81b3d-1e7d-4a72-a6b0-ad946e0c0965",
"email": "[email protected]",
"password": "very_secure_password",
},
{
"id": "55c81b3d-1e7d-4a72-a6b0-ad946e0c0965",
"email": "[email protected]",
"password": "123",
}
```

3. Register a passkey:

* After logging in, register a passkey for the logged-in user.


4. Log out:
* After the passkey registration is successful, log out of the application.

5. Login with with a passkey

* On the login page, choose sign in with a passkey option to authenticate using a passkey.

## Support

Feel free to reach out to us on [Discord](https://hanko.io/community) if you get into any issues.

## License

This project is licensed under the MIT License.



3 changes: 3 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PASSKEYS_API_KEY=your-hanko-passkey-api-key
PASSKEYS_TENANT_ID=your-hanko-passkey-tenant-id

151 changes: 151 additions & 0 deletions backend/controllers/passkey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import {
startServerPasskeyRegistration,
finishServerPasskeyRegistration,
startServerPasskeyLogin,
finishServerPasskeyLogin,
listCredentials,
deleteCredential,
} from "../service/passkey.js";
import { getUserID } from "../service/get-user-id.js";
import { v4 as uuidv4 } from "uuid";
import { setUser } from "../service/auth.js";
import db from "../db.js";

async function handlePasskeyRegister(req, res) {
const { user } = req;
const userID = user.id;

if (!userID) {
return res.status(401).json({ message: "Unauthorized" });
}
console.log("userId", userID);

const { start, finish, credential } = req.body;

try {
if (start) {
const createOptions = await startServerPasskeyRegistration(userID);
console.log("registration start");
return res.json({ createOptions });
}
if (finish) {
await finishServerPasskeyRegistration(credential);
return res.json({ message: "Registered Passkey" });
}
} catch (error) {
return res.status(500).json(error);
}
}

async function handlePasskeyLogin(req, res) {
const { start, finish, options } = req.body;

try {
if (start) {
const loginOptions = await startServerPasskeyLogin();
return res.json({ loginOptions });
}
if (finish) {
const jwtToken = await finishServerPasskeyLogin(options);
const userID = await getUserID(jwtToken?.token ?? "");
console.log("userID from hanko", userID);
const user = db.users.find((user) => user.id === userID);
if (!user) {
return res.status(401).json({ message: "Invalid user" });
}
console.log("user", user);
const sessionId = uuidv4();
setUser(sessionId, user);
res.cookie("sessionId", sessionId);
return res.json({ message: " Passkey Login successful" });
}
} catch (error) {
console.error(error);
return res
.status(500)
.json({ message: "An error occurred during the passke login process." });
}
}

async function listPasskeyCredentials(req, res) {
const { user } = req;
const userID = user.id;

if (!userID) {
return res.status(401).json({ message: "Unauthorized" });
}

try {
const credentials = await listCredentials(userID);
return res.json({ credentials });
} catch (error) {
return res.status(500).json(error);
}
}

async function deletePasskeyCredential(req, res) {
const { user } = req;
const userID = user.id;

if (!userID) {
return res.status(401).json({ message: "Unauthorized" });
}

const { credentialID } = req.params;

try {
await deleteCredential(credentialID);
return res.json({ message: "Credential deleted" });
} catch (error) {
return res.status(500).json(error);
}
}

async function updatePasskeyCredential(req, res) {
const { credentialID } = req.params;
const { name } = req.body;
const tenantId = process.env.PASSKEYS_TENANT_ID;
const apiKey = process.env.PASSKEYS_API_KEY;
const baseUrl = `https://passkeys.hanko.io/${tenantId}/credentials/${credentialID}`;

if (!tenantId || !apiKey) {
return res.status(500).json({ message: "Server configuration error" });
}

const options = {
method: "PATCH",
headers: { apiKey: apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ name }),
};

try {
const response = await fetch(baseUrl, options);
if (!response.ok) {
throw new Error(`Failed to update credential: ${response.statusText}`);
}
// const responseData = await response.json();
if (
response.headers.get("Content-Type")?.includes("application/json") &&
response.ok
) {
const responseData = await response.json();
res.json({
message: "Credential updated successfully",
data: responseData,
});
} else {
console.error("Non-JSON response or error", response.statusText);
}
} catch (error) {
console.error(error);
res.status(500).json({ message: error.message });
}
}

export {
handlePasskeyRegister,
handlePasskeyLogin,
listPasskeyCredentials,
deletePasskeyCredential,
updatePasskeyCredential,
};
31 changes: 31 additions & 0 deletions backend/controllers/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { v4 as uuidv4 } from "uuid";

import { setUser, clearUserSession } from "../service/auth.js";
import db from "../db.js";

async function handleUserLogin(req, res) {
const { email, password } = req.body;
const user = db.users.find((user) => user.email === email);
if (!user || user.password !== password) {
return res.status(401).json({ message: "Invalid username or password" });
}
const sessionId = uuidv4();
setUser(sessionId, user);
console.log(sessionId);
res.cookie("sessionId", sessionId);
return res.status(200).json({ message: "Login successful" });
}

async function handleUserLogout(req, res) {
const sessionId = req.cookies.sessionId;
if (!sessionId) {
return res.status(400).json({ message: "No session to log out from" });
}

clearUserSession(sessionId);

res.clearCookie("sessionId");
return res.status(200).json({ message: "Logout successful" });
}

export { handleUserLogin, handleUserLogout };
21 changes: 21 additions & 0 deletions backend/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const db = {
users: [
{
id: "b3fbbdbd-6bb5-4558-9055-3b54a9469629",
email: "[email protected]",
password: "password123",
},
{
id: "22c81b3d-1e7d-4a72-a6b0-ad946e0c0965",
email: "[email protected]",
password: "very_secure_password",
},
{
id: "55c81b3d-1e7d-4a72-a6b0-ad946e0c0965",
email: "[email protected]",
password: "123",
},
],
};

export default db;
Loading

0 comments on commit c943a9b

Please sign in to comment.