How to Implement Passkey Authentication and Fine-Grained Authorization in JavaScript
Tags: javascript, auth, passkeys, security
Published: 2023-10-27 10:16:30

---
title: How to Implement Passkey Authentication and Fine-Grained Authorization in JavaScript
published: true
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43fhyaawlzljy76avc5k.png
date: 2023-10-27 10:00:00 UTC
tags: javascript, auth, passkeys, security
canonical_url: https://www.permit.io/blog/better-access-control-with-passkeys-and-fine-grained-authorization
---

## Introduction

Some applications maintain the same access control for years. You use a Login and password to sign in, and then you are permitted to perform operations based on Access Control Lists (ACLs) and flat Roles. The modern era of cloud applications, and the fact that the internet is a space where everything is public, makes this model obsolete. In a world where everyone can access everything, and passwords can be cracked within minutes, we must find new ways to redefine access control.

In this article, we’ll build a full-stack application that demonstrates two new trends in access control: **Passkeys** and **Fine-Grained Authorization**.

_Let's start with the details of what each of these terms mean._

## Authentication - Who Are You?

The first phase in access control is authentication - verifying the identity of the user.  
Previously, this verification could be completed by providing an answer to a rather basic question - `What do you know?`. This question could have been answered by providing a secret only the user should know - like a password. 

These days, data can be easily stolen and exposed to the whole internet - thus, assuming only the user knows the password is not enough.

To strengthen the question of `Who are you?`, another question has popped up as a trend in recent years - `What do you have?`

This question is an example of two-factor authentication. It requires the user to provide a second secret they possess, like a mobile phone or a security key. While this method might be sufficient in some cases, [<u>vulnerabilities such as MFA fatigue and others in recent cyber attacks</u>](https://www.exabeam.com/explainers/insider-threat/how-mfa-fatigue-attacks-work-6-ways-to-defend-against-them/#:~:text=A%20multi%2Dfactor%20authentication%20(MFA,phone%2C%20or%20other%20registered%20devices. "https://www.exabeam.com/explainers/insider-threat/how-mfa-fatigue-attacks-work-6-ways-to-defend-against-them/#:~:text=A%20multi%2Dfactor%20authentication%20(MFA,phone%2C%20or%20other%20registered%20devices.") have proven the second authentication factor could be exploited as well. 

To solve that, we try today to answer those two questions, but also the overall question of `Who are you?` with a third factor - something that proves that the user is indeed who they claim to be. This can be achieved by using biometric data in the [<u>form of </u><u><strong>Passkeys</strong></u>](https://blog.google/technology/safety-security/the-beginning-of-the-end-of-the-password/ "https://blog.google/technology/safety-security/the-beginning-of-the-end-of-the-password/").

 ![mfa.jpeg](https://media.graphassets.com/OVP6NU95Sq2DFgt4ZLmC "mfa.jpeg")
## Authorization - What You Can Do?

After we manage to identify the user and confirm that they are indeed who they claim to be, the next step is to decide **what can they do within our application**. 

 ![authz.jpeg](https://media.graphassets.com/Izn3oGQrQoKnJSfreZLd "authz.jpeg")

Previously, this question was answered with a simple list of roles and a list of permissions for each role. Today, in a world of distributed applications, we are just using multiple sources for gathering the data we need to streamline our permissions model into a simple list of roles. Not only that, in the world of SaaS, applications depend on 3rd party products (such as Salesforce, for example), creating more factors for policy decisions.

To address this need, it is crucial to apply a more fine-grained approach. Instead of using roles, we define a more complex policy [<u>based on attributes</u>](https://www.permit.io/blog/what-is-abac "https://www.permit.io/blog/what-is-abac") (ABAC) and [<u>relationships between entities (ReBAC)</u>](https://www.permit.io/blog/what-is-rebac "https://www.permit.io/blog/what-is-rebac"). This way, we can declare policy rules that calculate multiple vectors of data from many factors and decide whether a user can perform an operation or not.

While many developers find themselves mixing this permissions model with their application code, the best practice is to separate the authorization logic from the application code. Two methods we can implement to achieve this are [<u><strong>Policy as Code</strong></u>](https://www.permit.io/blog/what-is-policy-as-code "https://www.permit.io/blog/what-is-policy-as-code") and **Policy as Graph** , where the policy is defined in a separate file and the data saved in a dedicated Graph DB. This gives developers the ability to define granular and generic permission models that are easily enforced by the application.

## The Demo Application

To better demonstrate these methods, we will build a simple note-taking application using two tools - **Hanko** and **Permit.io**.

 ![App Screenshot.jpg](https://media.graphassets.com/E8n9e7aQUevArwSdB09P "App Screenshot.jpg")

As you can see, the functionality of the application is very simple. An authenticated user can create notes, and each note has a title and a body. We also want to configure the notes in a way that only the user who created a note (or an admin) could delete it.

Instead of building these passkeys and fine-grained authorization from scratch, we will use the following tools:

[Hanko](https://www.hanko.io "https://www.hanko.io") - a cloud service and open-source tool that allows us to implement passkey authentication.

[Permit.io](https://www.permit.io "https://www.permit.io") - a cloud service and open-source tool that allows us to implement fine-grained authorization without having to build it from scratch ourselves.

The application code is built with Next.js, and [the code is available on GitHub](https://www.github.com/permitio/permit-hanko/ "https://www.github.com/permitio/permit-hanko/").

## Setup the Application

In the following sections, we will go step-by-step through the implementation of **passkeys** and **fine-grained authorization** in our application. We highly recommend cloning the sample application so you can follow the steps more easily. 

If you prefer to read the article without cloning the application, you can skip this section and continue to the next one.

Clone the application:

```bash
git clone git@github.com:permitio/permit-hanko.git
```

Install the dependencies:

```bash
npm install
```

Run the application:

```bash
npm run dev
```

At this point, the application will fail to run as we need to set up the Hanko and Permit.io services.

 ![Hanko - App error.jpg](https://media.graphassets.com/AibzdKbVROGMUhDoGqho "Hanko - App error.jpg")

Let's set up the Hanko account so we can log in to the application.

## Use Hanko for Passkey Authentication

To use Hanko for passkey authentication, you can either run it locally in your environment or use the cloud service. In this article, we will use the cloud service as it is easier to set up and use.

1. Visit the [Hanko webapp](https://cloud.hanko.io/ "https://cloud.hanko.io/"), create a new organization, and give it the name you want.
  ![Hanko - New organization.jpg](https://media.graphassets.com/I6XOj3L6TBe48xsaaTUh "Hanko - New organization.jpg")
2. In the main dashboard, create a new project - assign `http://localhost:3000` as the App URL.
  ![Hanko - New Project.jpg](https://media.graphassets.com/OK2gizgmSJmB03elxgRn "Hanko - New Project.jpg")
3. From the `Settings` > `General` section of the project, copy the API URL.
  ![Hanko API URL.jpg](https://media.graphassets.com/tGbycDn8T12QBRPKUygH "Hanko API URL.jpg")
4. Paste it to a new file called `.env.local`, in the root directory of the application.
  ```bash
  NEXT_PUBLIC_HANKO_API_URL=https://a0ae8d5d-9505-415f-ad70-51839c285726.hanko.io
  ```

Now, when we run the application again, we can see that the error is gone, and we can see the login page.

 ![Hanko - Login.jpg](https://media.graphassets.com/XS4MN8tSPm6Eq2odhmN4 "Hanko - Login.jpg")

To add this authentication window, we just use the Hanko SDK for JavaScript. You can see the element that implemented the login flow in the [app/auth/login/page.tsx](https://github.com/permitio/permit-hanko/blob/main/src/app/auth/login/page.tsx "https://github.com/permitio/permit-hanko/blob/main/src/app/auth/login/page.tsx") file.

```tsx
<Paper sx={{ p: 2 }}>
  <HankoAuth />
</Paper>
```

We also added a middleware logic in the [middleware.ts](https://github.com/permitio/permit-hanko/blob/main/src/middleware.ts "https://github.com/permitio/permit-hanko/blob/main/src/middleware.ts") file that will redirect the user to the login page if they are not authenticated.

```ts
const authenticateUser = async (req: NextRequest): Promise<string> => {
 if (!hankoApiUrl) {
  return "";
 }

 // Get Hanko token from cookie
 const hanko = req.cookies.get("hanko")?.value;
...
// Authenticate user using Hanko
const user = await authenticateUser(req);

// Redirect to login page if user is not authenticated
if (!user) {
 urlToRedirect.pathname = LOGIN_URL;
 return NextResponse.rewrite(urlToRedirect);
}
```

With this authentication configuration and flow configured, we are ready to continue with the implementation of Permit.io for authorization in our application.

## Use Permit.io for Basic RBAC Authorization

Now, that we are done with the authentication, it is time to set up our permission layer. For the first phase, we will use simple roles to determine the actions that the user can do.

In the [/app/api/notes/route.ts](https://github.com/permitio/permit-hanko/blob/main/src/app/api/notes/route.ts "https://github.com/permitio/permit-hanko/blob/main/src/app/api/notes/route.ts"), you'll find four functions, `GET`, `POST`, `PUT`, and `DELETE` - responsible for the logic of getting, creating, updating, and deleting notes, respectively.

Traditionally, developers would check the user's role in the application code, and decide if the user can perform the operation or not. For example, in our `GET` function, they used code that looks like this:

```ts
if (user.admin !== true) {
 return;
}
```

This code might be simple, but it is not scalable. If we want to change the role permissions, we need to change the code and redeploy the application. Also, if we would like to make this "flat" permission more fine-grained, we need to add more code and make it more complex.

If you look at the `route.ts` file, you'll not find any of those checks. Instead, we are using the generic **permit.check** [middleware.ts](https://github.com/permitio/permit-hanko/blob/main/src/middleware.ts "https://github.com/permitio/permit-hanko/blob/main/src/middleware.ts") file.

```ts
const response = await fetch(`${pdpUrl}/allowed`, {
 method: "POST",
 headers: {
  Authorization: `Bearer ${permitApiKey}`,
  "Content-Type": "application/json",
  Accept: "application/json",
 },
 body: JSON.stringify({
  user: user,
  action,
  resource: resource,
  context: {},
 }),
});
```

This code is a generic permit.check function that checks the permissions configured for the application using three factors:

**User** - the entity that attempts to perform the operation (in our case, a user authenticated with Hanko).

**Action** - the operation that the user will attempt to perform (in our case, `GET`).

**Resource** - the entity that the user will attempt to perform the operation on (in our case, the note).

> At this point, since we haven't configured the Permit SDK key in the app, any user can perform any operation. Setting up Permit.io will fix this.

Now, we can simply configure these permissions in Permit.io, and change them for our needs without changing the application code.

1. Create a free account on Permit.io website, then create a new organization, and give it the name you want. Skip the rest of the Onboarding wizard

2. In the left sidebar, click on **Policy** and then go to the **Roles** tab, create the following roles:
  ![Permit Roles.jpg](https://media.graphassets.com/mJ04lbKSkqfxbN9WL2rY "Permit Roles.jpg")

1. In the **Resources** tab, create a new resource called notes, with the following four actions and an owner attribute:
  ![Hanko Permit - Resources.jpg](https://media.graphassets.com/XpIUxSjITIiZXVrgkLgW "Hanko Permit - Resources.jpg")
2. Back in the **Policy** tab, let's allow all users to `GET` and `POST` notes, and only users with the admin role to `PUT`, and `DELETE` notes.
  ![Hanko Permit - RBAC.jpg](https://media.graphassets.com/ddceVLjRe267wKFu8Ctx "Hanko Permit - RBAC.jpg")

Next, we need to place the Permit.io API credentials in our application config.

1. To use Permit.io in our application, we need first to get the API key from Permit.io dashboard.

2. In the `env.local` file, add the following key
    ```bash
    PERMIT_API_KEY=<YOUR_COPIED_API_KEY>
    ```
3. We will also want to configure the API endpoint of Permit.io to check the permissions, we will use now in the cloud service.
    ```bash
    PERMIT_PDP_URL=https://cloudpdp.api.permit.io
    ```

To make sure all environment variables are in place, restart the application.

Now, as we configured the permissions in Permit.io and the app, it is time to sign up with a user and try it out.

## Check the Permissions

In `localhost:3000` page, login with a user of your choice, and make sure you have the right passkey configured. 

In the first screen, let's try to create a note - we can see this note is now on the list.

 ![Permit - Hanko create note.jpg](https://media.graphassets.com/ImR07wNKQE2TQ8pykhwX "Permit - Hanko create note.jpg")

In the Permit.io dashboard, let's go to the **Audit** tab in the left sidebar, and we can see that the `POST` action is logged. Opening the audit log row, we can see detailed information about this decision, and that the decision happened because this user has the User role.

 ![Permit - Audit log.jpg](https://media.graphassets.com/2szuEbAQbyBcipmdUmkQ "Permit - Audit log.jpg")

The way this user got the user role is a sync function we created for every user in our system as the least privileged role.

```ts
const response = await permit.api.syncUser({
 key,
 email,
 attributes: {
  roles: ["user"],
 },
});

await permit.api.roleAssignments.assign({
 role: "user",
 tenant: "default",
 user: key,
});
```

Now, let's try to delete the note - and we can see that the delete button is returning an error

 ![Screenshot Template.jpg](https://media.graphassets.com/hdSJ7z8hS0aHqjSJOi1Z "Screenshot Template.jpg")

To change this situation, let's go to the Permit.io dashboard, in the `Users` screen edit our user and add an admin role to it.

 ![Screenshot Template-1.jpg](https://media.graphassets.com/OUSHJvozRzegnRGnmFQ1 "Screenshot Template-1.jpg")

Returning to the application, we can see that the delete action is now **working**.

By this simple configuration change, we can see how we can change the permissions of our application without changing the code.

Now that we are done with the RBAC model in our application, let's go to the next phase and make our permissions more fine-grained with no need to change the application code.

## Use Permit.io for Fine-Grained ABAC Authorization

_Note: For this section to run properly, you need to run the Permit.io PDP as a sidecar,_ [_You can read how to do that here_](https://docs.permit.io/how-to/deploy/deploy-to-production "https://docs.permit.io/how-to/deploy/deploy-to-production")_._

As stated before, the delete action in our app is supposed to be limited to the same user who created the role. 

To achieve that, we can easily configure this policy in Permit.io and see its immediate effect in the application. 

1. In the Permit.io dashboard, go to **ABAC Rules** on the **Policy** page, and enable the ABAC option.
  ![Permit enable ABAC.jpg](https://media.graphassets.com/mP37RST6sP1oq5RKwIAJ "Permit enable ABAC.jpg")
2. Create a new **Resource Set** rule - this set will create a condition that will be true only if the user is the owner of the note.
  ![Permit ABAC rule.jpg](https://media.graphassets.com/4sPfg0r6RqCZkCsOUt1O "Permit ABAC rule.jpg")
3. Back in the **Policy** tab, create a new policy that will allow `PUT`, and `DELETE` only for the owner of the note. Mind that we leave the privileged admin role to do all actions.
  ![Permit Hanko ABAC policy.jpg](https://media.graphassets.com/dHOWf0i7RG01XlFvI9m4 "Permit Hanko ABAC policy.jpg")

At this point, let's add another user to the application. Let's log out with the current user and sign up with a new user. Let's now try to remove the note that we created with the first user - and we can see that the delete button is returning an error.

![ABAC no matching.jpg](https://media.graphassets.com/tK7S8WSJQ4cmzKJTOJpw "ABAC no matching.jpg")

Let's now create a new note from this user, to see how we combine the RBAC and ABAC permissions together in our application.

1. Create a new note with the second user.

2. Log out from the second user and log in with the first user.

3. Try to delete the note that we created with the second user - and we can see that delete is successful as the first user is an admin.

If we compare this simple configuration change to the traditional way of implementing permissions, we can see how much time and effort we saved. We also created a more generic and fine-grained permissions model that can be easily changed and configured without changing the application code.

## Use Permit.io for Fine-Grained ReBAC Authorization

Another approach for fine-grained authorization that we can use in our application is ReBAC - Relationship-Based Access Control. Assuming a note app, we might want to create workspaces, organizations, and folders for our notes. In this case, we might do not have a dedicated owner field in the note entity, but we have a relationship between the note and the workspace.

As Permit.io also supports the configuration of ReBAC policies, we can easily implement it in our application without changing the permit.check or any enforcement code in the app. To read more about ReBAC and modeling such fine-grained permissions, [read here](https://docs.permit.io/modeling/google-drive "https://docs.permit.io/modeling/google-drive").

## Next Steps

In this article, we demonstrated how to implement passkey authentication and fine-grained authorization in a simple note-taking application. We used [https://hanko.io](Hanko) and [https://www.permit.io/rebac](Permit.io) to implement those trends in our application, and we can see how easy it is to implement them with no need to change the application code.

Access control is a very important part of our application, and we must make sure that we implement it in the right way. As next steps, we recommend you to read more on the following topics:

- [Policy as Graph vs. Policy as Code](https://www.permit.io/blog/zanzibar-vs-opa "https://www.permit.io/blog/zanzibar-vs-opa")

- [ABAC vs. RBAC vs. ReBAC](https://www.permit.io/blog/rbac-vs-abac-vs-rebac "https://www.permit.io/blog/rbac-vs-abac-vs-rebac")

- [Passkeys vs. Passwords vs. MFA](https://www.passkeys.io/ "https://www.passkeys.io/")

We would also be happy to see you in our Slack community, where you can ask questions and get help from our team and other developers. [Join us here](https://io.permit.io/FoAz-community "https://io.permit.io/FoAz-community").