Block Access Based on IP Location

Autor:Lisa Farrell · 2026-01-18

A frontend access-control solution based on IP location: when the page loads, it requests https://my.ipin.io/info to obtain the visitor’s country code (country). When the country is identified as TW (or other regions you configure), the page stops providing normal content and instead terminates the access flow directly on the frontend.

Many “restrict access by region” requirements are not actually about building strong security (the anti-hacker kind), but rather common frontend scenarios:

  • Service is temporarily unavailable in certain countries/regions (for compliance, copyright, payments, logistics, etc.)
  • Perform a region check as soon as the user enters the page, then decide whether to show content/buttons/download links
  • Marketing campaigns are only available in specific regions; other regions need a direct “out of service area” or “unavailable” message

This approach is particularly suitable for the frontend: the browser directly requests GET https://my.ipin.io/info, and the API returns geolocation information based on the “IP that initiates the request”, for example:

{"ip":"185.220.236.7","country":"TW","region":"Taiwan","city":"Taipei"}

Then you only need to check `country === "TW"` to perform the “block access” action (for example: show an access-denied message, output 404 text, or overwrite the original page with your own error-page structure). The key point here is: we are not discussing “redirects”; instead, we clearly present the access result as an error or refusal, so what the user sees is the final “not accessible” state.

1) Logic and Working Principle (2 paragraphs)

Paragraph 1: Flow
The user opens the web page → the browser immediately requests https://my.ipin.io/info → obtains `country` → if it matches the blacklist (e.g., `TW`), it immediately blocks subsequent page rendering: it no longer renders normal content, but directly outputs a final-state message such as “Access denied / 404 / custom error”; otherwise, it continues to load page features and content normally.

Paragraph 2: Why the frontend fits this API
Because the browser requests a third-party API directly, the third party sees the visitor’s “public IP”, and the returned country/region corresponds to the user themselves—this is exactly the effect you want.

2) Code Implementation (as simple as possible, frontend-focused)

2.1 The simplest: block on match (recommended)

Put this in the page <head> (the earlier, the better). Once matched, it overwrites the page content and shows a clear “Access Denied” message. You can replace the text with your own compliance notice or service availability statement.

Example A: block access if country === "TW"

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Access Denied</h1>" +
        "<p>Service is not available in your region.</p>" +
        "</body>";
    }
  } catch (e) {
    // If lookup fails, allow by default (you can change to block by default)
  }
})();
</script>

Example B: block access if NOT TW (only allow TW)

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country !== "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Access Denied</h1>" +
        "<p>This page is only available in selected regions.</p>" +
        "</body>";
    }
  } catch (e) {
    // If lookup fails, allow by default (you can change to block by default)
  }
})();
</script>

Example C: block access if TW, US, etc. (blocklist)

<script>
(async function () {
  const blocked = ["TW", "US"]; // Block if matches any
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (blocked.includes(info.country)) {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Access Denied</h1>" +
        "<p>Service is not available in your region.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.2 Output 404 Not Found (a “unavailable/non-existent” presentation)

Some products prefer to appear as “page not found” for certain regions rather than explicitly stating “restricted”. In this case, you can overwrite the page content with a 404-style error-page structure. Note: this is still a frontend presentation and does not truly change the HTTP status code returned by the server, but it is intuitive enough for the page-access experience.

<script>
(async function () {
  try {
    const { country } = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>404 Not Found</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>404 Not Found</h1>" +
        "<p>The requested URL was not found on this server.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.3 Use an array blacklist (closer to real business needs)

In real-world scenarios, you usually don’t block only one region; instead, you maintain a blacklist. The example below remains minimal: if any match is found, it directly outputs “access denied”.

<script>
(async function () {
  const blocked = ["TW"]; // Country/region codes to block
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (blocked.includes(info.country)) {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Access Denied</h1>" +
        "<p>Service is not available in your region.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.4 React (shortest usable: show an error page after a match)

import { useEffect, useState } from "react";

export default function App() {
  const [blocked, setBlocked] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        const info = await fetch("https://my.ipin.io/info").then(r => r.json());
        if (info.country === "TW") setBlocked(true);
      } catch (e) {}
    })();
  }, []);

  if (blocked) {
    return (
      <div style={{ fontFamily: "system-ui", padding: 40 }}>
        <h1>403 Access Denied</h1>
        <p>Service is not available in your region.</p>
      </div>
    );
  }

  return <div>...</div>;
}

2.5 Vue (also shortest: show error content after a match)

<script>
export default {
  data() {
    return { blocked: false };
  },
  async mounted() {
    try {
      const info = await fetch("https://my.ipin.io/info").then(r => r.json());
      if (info.country === "TW") this.blocked = true;
    } catch (e) {}
  }
}
</script>

<template>
  <div v-if="blocked" style="font-family:system-ui;padding:40px">
    <h1>403 Access Denied</h1>
    <p>Service is not available in your region.</p>
  </div>

  <div v-else>...</div>
</template>

3) Implementing in WordPress / Joomla / Magento / Shopify (frontend approach + code)

The idea is the same everywhere: insert a “super-simple JS snippet” into the global site head (or layout file). Since your goal is “frontend control + error presentation”, as long as the script executes early enough, it can complete the region check before the main page renders, and if matched, directly present the final state: “access denied / 404 / error message”.

I recommend using this snippet universally (the simplest “access denied” version):

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 Access Denied</h1>" +
        "<p>Service is not available in your region.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

3.1 WordPress

  • Option A (easiest): use a “header/footer script injection” plugin and paste the script into the Header area (site-wide)
  • Option B: insert the script before </head> in the theme file `header.php`

3.2 Joomla! / Joomla

  • Typically, in the current template’s `index.php`, find <head> and paste the script before </head>
  • Or use a backend/template option that allows inserting custom code/custom HTML into the head (entry points vary by template)

3.3 Magento (frontend injection approach)

  • If you only want “presentation-layer blocking”, you can add the script to the theme’s global head (the head area in theme/layout)
  • Magento backends often also have an “HTML Head” or similar configuration (varies by version/theme) whose goal is to inject content into <head> site-wide

> Since your requirement is “avoid server-side code as much as possible”, we won’t expand on XML/module approaches here—if you can insert the script into the head, it will work.

3.4 Shopify

  • Online Store → Themes → Edit code
  • Find `theme.liquid` and paste the script before </head> (site-wide)

3.5 Other common systems (general pattern)

  • Any system that can edit the global <head>: place the script as early as possible in the head
  • If you only want to restrict certain pages: add the script only in the corresponding page templates

4) Summary

The core of this article can be summed up in one sentence:

> The browser requests https://my.ipin.io/info and obtains { country }. If country === "TW", it terminates page access directly by presenting “access denied / 404 / custom error”, thereby implementing a frontend solution for blocking access by IP region.

It is low-cost and quick to implement, and fits “compliance notices / regional unavailability notices / frontend traffic gating”. In many businesses, an “error presentation” at the frontend layer already satisfies product and operations goals: users clearly understand the page is unavailable in their region, and the main content and entry points are not further exposed. If you need stricter “non-bypassable” control, you can add an additional blocking layer at the server or CDN.

5) Frequently Asked Questions (FAQ)

Q1: Can frontend blocking really “deny access”?

More precisely, it allows the “presentation layer” to return a clear denial result to the visitor (such as a 403/404/error message page), preventing normal users from continuing to use page features. However, people who know what they’re doing can disable JavaScript or modify scripts, so it is not a non-bypassable security wall. If you need “absolute blocking”, you should add another blocking layer on the server or CDN. If your goal is compliance messaging and feature unavailability messaging, frontend blocking is usually sufficient.

Q2: Why put the script in the <head>, and as early as possible?

Because the earlier you check, the more you can terminate the access flow before the main content renders, reducing “content flashes” where users briefly see the page before it is blocked. It also avoids loading unnecessary resources (scripts, images, components). This is the most reasonable placement for both user experience and performance.

Q3: What if the https://my.ipin.io/info request fails?

The most common strategy is “allow by default” so that an external API failure does not mistakenly block normal users. Another strategy is “deny by default”, which fits strict compliance scenarios. You can choose based on business risk. In either case, keep the logic simple: if you successfully get country, evaluate it; if you cannot, follow your default policy.

Q4: Will there be CORS issues?

Possibly. If the API does not allow cross-origin access from browsers, fetch will be blocked by the browser. In that case, you must ensure the API enables CORS (for example, allowing your domain), otherwise the browser cannot read the returned JSON. For a “frontend-first” approach, CORS is the main thing you should confirm in advance.

Q5: What standard is the country value?

It is usually a two-letter country/region code (commonly ISO 3166-1 alpha-2). TW is a typical example. In practice, it is recommended to use uppercase codes consistently, and keep your blacklist in the same format to avoid mismatches caused by letter casing.

Q6: How do I switch from “blocking a country” to “only allowing certain countries”?

Just reverse the logic:
- allowlist: only allow ["US","JP"], and for all others show “access denied/error message”
- blocklist: only block ["TW"], and allow all others
Both approaches are essentially checking whether country is in a list; the difference is the default behavior.