Location-Based Access Control Made Easy with Next.js and IPInfo
Tags: nextjs, javascript, webdev
Published: 2023-07-18 14:29:52

---
title: Location-Based Access Control Made Easy with Next.js and IPInfo
published: true
date: 2023-07-18 14:30:00 UTC
tags: nextjs, javascript, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66mvmqzvy3i8zvfg6xrc.png
description: Easily add location-based access control to your application using IPinfo and Permit.io. Grant user access based on geolocation with this comprehensive guide.
canonical_url: https://www.permit.io/blog/location-based-access-control-made-easy-with-nextjs-and-ipinfo
---

In web applications, controlling access based on various factors is often necessary. One common requirement is **restricting access to certain content or features based on a user’s IP address**. This can be achieved using [Permit.io](http://permit.io "http://permit.io")’s low-code/no-code Authorization tool in conjunction with the [IPInfo](https://ipinfo.io/ "https://ipinfo.io/") service. This article will explore how to implement IP-based access control in a Next.js application.

The overarching concept behind this demo will be controlling access to a specific subdomain. Our primary objective is to **restrict access to the subdomain solely to users within our designated location**. Users outside a defined location will be **automatically redirected to the main page** if they attempt to access the subdomain.

## The tools we will be using

[IPInfo](https://ipinfo.io/ "https://ipinfo.io/") is a popular IP address data provider with over 10 contextualized datasets derived from IP addresses. It provides information such as the country, city, anonymous IPs, and the organization associated with an IP. IPInfo’s API can be accessed via various programming languages, including JavaScript, with their Node.js library.

[Permit.io](https://permit.io/ "https://permit.io/") is a flexible access control developer tool designed to simplify implementing various access control mechanisms in web applications. It allows you to define access rules based on roles or permissions and provides a simple `permit.check()`function to enforce them. With Permit, you can easily integrate access control into your Next.js applications.

In the following sections, we will guide you through setting up a Next.js project and implementing IP-based access control using Permit and IPInfo.

**Prerequisites** : This guide requires basic knowledge of Next.js and JavaScript.

**Important: ** This demo app will focus purely on the functionality and not the UI we will see.

## Let’s create a basic starter App in Next.js!

As a Developer Advocate at [Permit.io](https://io.permit.io/supertokens-passwordless-blog "https://io.permit.io/supertokens-passwordless-blog"), I have found that the most effective way to guide developers through the intricate realm of Authentication and Authorization is by demonstrating them through code and practical examples. Let’s dive into that.

To get started, you’ll need to have Node.js installed on your computer.   
Then, follow these steps:

1. Install the NextJS boilerplate, name your project, and select the setting that suits your development best. I always build my projects with the `/src` folder enabled and `lintr` installed. 

    ```bash
    npx create-next-app@latest
    ```
2. Navigate to the newly created directory.
    ```bash
    cd YOUR_PROJECT_NAME
    ```
3. Run the development server.
    ```bash
    npm run dev
    ```

Now you should see your Next.js application running on `http://localhost:3000`. Congratulations!

 ![supertokens-1.png](https://media.graphassets.com/i3EVgiJYRzuiXMyQJAE4 "supertokens-1.png")

## Creating a new page in Next.js

Once you have your repository open, we need to create a new folder under `/src` called `/pages`. Inside this folder, we can add paths on top of our URL that will display the relevant pages. Let’s create a file called `/only-uk`.

Inside the file, paste this simple code and see it in action.

```jsx
import React from "react";

export default function UnitedKingdomOnly() {
 return (
  <h1>If you are currently in the UK you will be able to see this page.</h1>
 );
}
```

It works :)

 ![ip-info-1.png](https://media.graphassets.com/OnGyGenSOuTGQRb5culb "ip-info-1.png")
## Setting up Permit and our first policy

We will need to create an ABAC policy within Permit. ABAC ([Attribute-based access control](https://www.permit.io/blog/what-is-abac "https://www.permit.io/blog/what-is-abac")) is an authorization model that evaluates attributes (or characteristics), alongside roles, resources, and actions to determine access. The purpose of ABAC is to allow users to define more complex access-control rules to prevent other users from unauthorized actions — those that don’t have “approved” characteristics as defined by an organization’s security policies.

For this demo, we can create three roles, a simple `Admin` role, an `Admin from the UK` and an `Admin from Poland`. As you can see, the two latter roles have IP location-based attributes.

First, go ahead a create an account at [https://app.permit.io](https://app.permit.io/ "https://app.permit.io/"). You will be presented with the onboarding, but once you enter your organization name, you can just **skip** the setup.

Once in the dashboard, navigate to the policy screen and manage our roles.

 ![supertokens-9.png](https://media.graphassets.com/A0Ap1yPmRxqowufcvkgQ "supertokens-9.png")

It’s time to create our first role. Each role will have specific rules associated with it, of what a user can and cannot do. We must create the `Admin` role first, as it will later serve as a building block for our ABAC conditions.

If you want to learn more about building an ABAC condition — check out our guide [here](https://docs.permit.io/features/policy-editor/abac/overview "https://docs.permit.io/features/policy-editor/abac/overview").

 ![ip-info-2.png](https://media.graphassets.com/9jUYKbubTyKvEVTNSFT6 "ip-info-2.png")

 ![ip-info-3.png](https://media.graphassets.com/4yAozS3TbSBAQpR3Lm0h "ip-info-3.png")

Success!

Next, we need to create the two other roles with attributes (or otherwise known in Permit as `User Sets.`

`User Sets` are _groups of users_ that adhere to _pre-defined attribute-based conditions_. These are conditions based on individual user characteristics.

However, before we jump into creating the User Sets, we need to define the attributes that will also be used for building our conditions. In this case, the attribute we will need to add will be `ip_location`. 

`Attributes` are values that serve as a declaration of what the condition rule will be. 

Now — ABAC is not enabled by default within Permit, so we need to switch the toggle. In the policy screen, at the top, you will see a tab called `ABAC Rules`. Click on it and toggle ABAC on.

 ![ip-info-4.png](https://media.graphassets.com/fVPtBVe0SRuDX1QrXo9c "ip-info-4.png")

Now as we navigate to the Users panel in Permit, a new tab will appear called `Attributes`. Click on it, and add a new user attribute called `ip_location` with the type of `String`. We need to define the type — this is a declaration of the value we will compare the attribute to. 

 ![ip-info-5.png](https://media.graphassets.com/vjAODwSHCLgJ0nOQkfgf "ip-info-5.png")

While we are in the Users panel — let’s add a sample user. In general, this would be the user and their unique ID that you would get from the JWT (JSON Web Token) upon successful authentication, but for this demo we will just fake that process and pretend it has already happened. 

If you need suggestions on the best Authentication Providers to work with, just [**message us on**  **Slack**](https://io.permit.io/permitslack "https://io.permit.io/permitslack"), and we will be more than happy to suggest some. 

If you would like to check out a guide on how to add a user to Permit after successful authentication with [Auth0](https://auth0.com/ "https://auth0.com/"), check out the article [here](https://www.permit.io/blog/add-rbac-authorization-to-auth0 "https://www.permit.io/blog/add-rbac-authorization-to-auth0").

 ![ip-info-6.png](https://media.graphassets.com/eB6f4HEyQUOuzlXnkWg1 "ip-info-6.png")

You can name the user however you’d like.

Now that we have the attribute and user set up, we can continue creating the two other roles (with conditions) — which means we will be creating `User Sets`.

Navigate to the User panel and into the `ABAC Rules` tab. Here, let’s create a new User Set. 

 ![uk-admin.png](https://media.graphassets.com/e6OqiEobQYieAioFg9Zb "uk-admin.png")

Our `UK Admin` role needs extra conditions, so let’s create a condition group. We stated that the `ip_location` is equal to the country code `UK`.

 ![ip-info-7.png](https://media.graphassets.com/NUNUHwI1TxiXMxjRTeV5 "ip-info-7.png")

Now you can do the same for the `Polish Admin` role.

 ![poland-admin.png](https://media.graphassets.com/oVIGT6m0QtGtoQIA1LUx "poland-admin.png")

Well done! You should now see two User Sets.

 ![ip-info-8.png](https://media.graphassets.com/ZotpapOsRkqZFwe9fxq2 "ip-info-8.png")

Great work so far. The last part that we need to create in the no-code dashboard is a `resource` and the `actions` we want to allow the roles to perform on the resource. The resource will be our restricted page called `only-uk` , and the action will be `view`. You can have as many actions as you want, but in order to keep this demo very simple, we will just create one.

In order to create a resource, navigate to the policy panel and click on the `Resources` tab and `create a resource`. 

A resource is the target object we want to authorize access to. It’s what the user will or won’t be able to perform actions on. 

 ![ip-info-9.png](https://media.graphassets.com/DvpCt5hnQxSDrtHdLu1w "ip-info-9.png")

Once the resource is created and as we navigate to the main Policy Editor, you should see something like this:

 ![abac-policy-undelected.png](https://media.graphassets.com/uBHSuQuzT4SeyWPb5531 "abac-policy-undelected.png")

Our policy is finished. Now, let’s enforce it in the UI. The only role that should be able to view the restricted page is `UK Admin`. Let’s tick the `view` box. 

 ![abac-selected.png](https://media.graphassets.com/vCyYCWA1QF2H1LGiU6FA "abac-selected.png")

Voila! Our Permit Policy configuration is finished!

## Installing dependencies

Next, install the necessary dependencies by running the following command:

```bash
npm install permitio ipinfo swr --save
```

## Initializing the Permit instance and running the PDP

When writing the code, we will need to consider two things:

1. Creating an API endpoint that will handle the authorization and IP fetching.
2. Adjust the restricted page to show a message based on the result of the enforcement. 

Inside of our `/pages` folder, let’s create another folder called `/api` , and inside that folder, we need to create a file — let’s call it `restrict.js`. This will become the URL endpoint that we call from our frontend, and it will appear under the endpoint path `/api/restrict`.

Inside the restrict folder, we need to import Permit and initialize the instance. 

```javascript
import { Permit } from "permitio";

const permit = new Permit({
 pdp: "http://localhost:7766",
 // your API Key
 token: process.env.PERMIT_API_KEY,
});
```

To be able to use ABAC within Permit, we have to deploy the PDP (Policy-Decision-Point).

Just run these two commands in your terminal to download the container and run it:

```bash
$ docker pull permitio/pdp-v2:latest
$ docker run -p 7766:7000 --env PDP_API_KEY=<YOUR_API_KEY> permitio/pdp-v2:latest
```

Make sure to replace the API Key inside the Permit instance and the docker run command. You can find your PermitAPI Key by clicking on your profile image and copying the key.

 ![supertokens-8.png](https://media.graphassets.com/YDDGQ9CZRPqx7hXyr97w "supertokens-8.png")
## Coding the Permit ABAC enforcement check

In order to restrict access, we need to include the `permit.check()` function in our code to check a specific user against our configured policy. 

Let’s add the below code to our `restrict.js` file.

```javascript
import { Permit } from "permitio";

const permit = new Permit({
 pdp: "http://localhost:7766",
 // your API Key
 token: process.env.PERMIT_API_KEY,
});

#################### ADD CODE BELOW ###################

export default async function enforceAccess(req, res) {

 const allowed = await permit.check(
  {
   key: "demo_user@gmail.com",
   attributes: {
    ip_location: SOME_LOCATION,
   },
  },
  "view",
  {
   type: "only-uk",
   tenant: "default",
  }
 );

 res.status(200).send({ allowed });

 return allowed;
}
```

Inside the `permit.check()` function, we pass three parameters: the `user object with attributes` that we are checking for, the `action` being performed and then `resource object`, passing in the `resource name` and the `tenant`.

A `tenant` is as a silo of resources and users; which in policy terms means only users within a tenant can act on the resources within the tenant. Tenants are isolated from one another.

Now if we look at the code, as part of the passed-in attributes, we are missing the IP location — which we are currently not handling at all. Not to worry, we have IPInfo to help us!

## Creating an account with IPInfo

To utilize the IPInfo service, you must sign up for an account and acquire an Access Token. IPInfo offers 50,000 free geolocation requests every month. Visit the[IPInfo page](https://ipinfo.io/ "https://ipinfo.io/"), sign up for a free account, and enter the dashboard:

 ![Screenshot 2023-06-13 at 15.01.37.png](https://media.graphassets.com/1BbxH7Q5yMvwG9g2kW2g "Screenshot 2023-06-13 at 15.01.37.png")

In general, IPInfo is very simple to set up, and you can start using it within minutes. If you check out their developer [documentation](https://ipinfo.io/developers "https://ipinfo.io/developers"), they give you many ways to run a simple request to fetch the user country and IP.

We will be using the `async/await` Fetch API to get the user country code.

As we are dealing with an access token here, it’s always best practice to store these tokens in an `.env` file to ensure you don’t accidentally share sensitive information.

 ![ip.png](https://media.graphassets.com/j7RKUjhmTfWBvaFPn7xe "ip.png")

I have an `.env.local` file setup with the below configuration:

```bash
PERMIT_API_KEY=
IP_INFO_TOKEN=
```

## Fetching a users IP address and Country Code

Now let’s add a function into the `/restrict.js`file to fetch the IP of the current user trying to access the restricted /`only-uk` page.

```javascript
const fetchLocationByIP = async () => {
 const request = await fetch(
  `https://ipinfo.io/json?token=${process.env.IP_INFO_TOKEN}`
 );
 const jsonResponse = await request.json();

 console.log(jsonResponse.ip, jsonResponse.country);

 return jsonResponse.country;
};
```

The above code will fetch the user details, extract the IP and country, and return the two-letter country code.

Now we need to pass the returned country code to the`ip_location` attribute. The whole code should look something like the example below.

## Making it all work together

```javascript
import { Permit } from "permitio";

const permit = new Permit({
 pdp: "http://localhost:7766",
 // your API Key
 token: process.env.PERMIT_API_KEY,
});

const fetchLocationByIP = async () => {
 const request = await fetch(
  `https://ipinfo.io/json?token=${process.env.IP_INFO_TOKEN}`
 );
 const jsonResponse = await request.json();

 console.log(jsonResponse.ip, jsonResponse.country);

 return jsonResponse.country;
};

export default async function enforceAccess(req, res) {

 // Getting the country code and storing in a variable
 const IPlocation = await fetchLocationByIP();

 const allowed = await permit.check(
  {
   key: "demo_user@gmail.com",
   attributes: {
    // Passing the variable to the ip_location attribute
    ip_location: IPlocation,
   },
  },
  "view",
  {
   type: "only-uk",
   tenant: "default",
  }
 );

 res.status(200).send({ allowed });

 return allowed;
}
```

Great! Now, as a final step, let’s edit the`/only-uk` page to handle the response correctly and display the appropriate message.

```jsx
import React from "react";
import useSWR from "swr";

export default function UnitedKingdomOnly() {
 const fetcher = (url) => fetch(url).then((res) => res.json());
 const { data, error } = useSWR("/api/restrict", fetcher);

 if (error) return <div>Failed to load.</div>;
 if (!data) {
  return <div>Loading...</div>;
 } else {
  if (data.allowed) {
   return (
    <div>
     If you are currently in the UK you will be able to see this page.
    </div>
   );
  } else {
   return <div>User is not based in the UK.</div>;
  }
 }
}
```

As I am currently in the UK myself, so if I try to access the page, I will get the below message:

 ![ip-info-1.png](https://media.graphassets.com/OnGyGenSOuTGQRb5culb "ip-info-1.png")

However, now I will change my location to Poland using a VPN, and as I refresh the page and try to reaccess the page, we will see this:

 ![Screenshot 2023-06-13 at 14.53.33.png](https://media.graphassets.com/ncBEjm8eQbaxgiIO25pq "Screenshot 2023-06-13 at 14.53.33.png")

Hurray! Our application now uses both IPInfo to fetch the user's current location, and Permit to enforce the access based on the location.

Learn more about [Permit](https://permit.io "https://permit.io") or [IPInfo](https://ipinfo.io "https://ipinfo.io") — or access the whole code repository for this project [here](https://github.com/filipermit/demo-projects/tree/main/ip-based-access-control "https://github.com/filipermit/demo-projects/tree/main/ip-based-access-control").