Bloquer l’accès en fonction de la localisation IP

Auteur:Lisa Farrell · 2026-01-18

Une solution de contrôle d’accès côté frontend basée sur la localisation IP : lors du chargement de la page, une requête est envoyée vers https://my.ipin.io/info afin d’obtenir le code pays du visiteur (country). Lorsque country est identifié comme TW (ou d’autres régions que vous configurez), la page ne fournit plus de contenu normal et met fin au flux d’accès directement côté frontend.

De nombreuses demandes de type « restreindre l’accès par région » ne visent pas à construire une sécurité forte (au sens anti-piratage), mais correspondent plutôt à des scénarios frontend très courants :

  • Le service n’est pas disponible temporairement dans certains pays/régions (conformité, droits d’auteur, paiements, logistique, etc.)
  • Effectuer une vérification de région dès l’arrivée de l’utilisateur sur la page, puis décider d’afficher ou non le contenu/boutons/liens de téléchargement
  • Les campagnes marketing ne ciblent que des régions spécifiques ; les autres régions doivent recevoir un message direct « hors zone de service » ou « indisponible »

Cette approche est particulièrement adaptée au frontend : le navigateur envoie directement GET https://my.ipin.io/info, et l’API renvoie des informations de géolocalisation en fonction de « l’IP qui initie la requête », par exemple :

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

Ensuite, il suffit de tester `country === "TW"` pour exécuter l’action « accès interdit » (par exemple : afficher un message d’accès refusé, afficher un texte 404, ou remplacer la page d’origine par votre propre structure de page d’erreur). Le point clé ici est le suivant : nous ne parlons pas de « redirection », mais nous présentons clairement le résultat d’accès sous forme d’erreur ou de refus, afin que l’utilisateur voie directement l’état final « inaccessible ».

1) Logique et principe de fonctionnement (2 paragraphes)

Paragraphe 1 : Flux
L’utilisateur ouvre la page web → le navigateur appelle immédiatement https://my.ipin.io/info → récupère `country` → si cela correspond à la liste noire (par ex. `TW`), la page bloque immédiatement la suite de l’affichage : elle ne rend plus le contenu normal et affiche directement un état final du type « accès refusé / 404 / erreur personnalisée » ; sinon, elle continue de charger normalement les fonctionnalités et le contenu.

Paragraphe 2 : Pourquoi le frontend convient à cette API
Comme le navigateur appelle directement une API tierce, le tiers voit « l’IP publique » du visiteur, et le pays/la région retourné correspond à l’utilisateur lui-même — c’est exactement l’effet recherché.

2) Implémentation du code (aussi simple que possible, orientée frontend)

2.1 Le plus simple : si ça correspond, “accès interdit” (recommandé)

Place ce code dans le <head> (le plus tôt possible). En cas de correspondance, il écrase le contenu de la page et affiche un message clair “Accès interdit”. Tu peux remplacer le texte par ton propre message de conformité ou de périmètre de service.

Exemple A : bloquer si 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 Accès interdit</h1>" +
        "<p>Le service n’est pas disponible dans votre région.</p>" +
        "</body>";
    }
  } catch (e) {
    // En cas d’échec, autoriser par défaut (tu peux changer pour refuser par défaut)
  }
})();
</script>

Exemple B : bloquer si ce n’est PAS TW (n’autoriser que 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 Accès interdit</h1>" +
        "<p>Cette page est disponible uniquement dans certaines régions.</p>" +
        "</body>";
    }
  } catch (e) {
    // En cas d’échec, autoriser par défaut (tu peux changer pour refuser par défaut)
  }
})();
</script>

Exemple C : bloquer si TW, US, etc. (liste de blocage)

<script>
(async function () {
  const blocked = ["TW", "US"]; // Bloquer si correspond à l’un des éléments
  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 Accès interdit</h1>" +
        "<p>Le service n’est pas disponible dans votre région.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.2 Afficher 404 Not Found (présentation « indisponible/inexistant »)

Certains produits préfèrent apparaître comme « page inexistante » dans certaines régions plutôt que d’indiquer explicitement « restreint ». Dans ce cas, vous pouvez remplacer le contenu par une structure de page d’erreur de type 404. Remarque : cela reste une présentation côté frontend et ne change pas réellement le code HTTP renvoyé par le serveur, mais c’est suffisamment clair pour l’expérience d’accès à la page.

<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 Utiliser une liste noire sous forme de tableau (plus proche d’un besoin réel)

En pratique, on ne bloque généralement pas une seule région : on maintient plutôt une liste noire. L’exemple ci-dessous reste minimal : en cas de correspondance, il affiche directement « accès refusé ».

<script>
(async function () {
  const blocked = ["TW"]; // Codes pays/régions à bloquer
  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 Accès refusé</h1>" +
        "<p>Le service n’est pas disponible dans votre région.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.4 React (la version la plus courte utilisable : afficher une page d’erreur après correspondance)

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 Accès refusé</h1>
        <p>Le service n’est pas disponible dans votre région.</p>
      </div>
    );
  }

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

2.5 Vue (également la version la plus courte : afficher un contenu d’erreur après correspondance)

<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 Accès refusé</h1>
    <p>Le service n’est pas disponible dans votre région.</p>
  </div>

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

3) Mise en œuvre dans WordPress / Joomla / Magento / Shopify (approche frontend + code)

L’idée est la même partout : insérer un « snippet JS ultra simple » dans le head global du site (ou dans le fichier de layout). Comme votre objectif est « contrôle frontend + présentation d’erreur », tant que le script s’exécute suffisamment tôt, il peut terminer la vérification de région avant le rendu principal, et en cas de correspondance, présenter directement l’état final « accès refusé / 404 / message d’erreur ».

Je recommande d’utiliser partout ce snippet (la version « accès refusé » la plus simple) :

<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 Accès refusé</h1>" +
        "<p>Le service n’est pas disponible dans votre région.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

3.1 WordPress

  • Option A (la plus simple) : utiliser une extension d’injection de scripts header/footer et coller le script dans la zone Header (sur tout le site)
  • Option B : insérer le script avant </head> dans le fichier de thème `header.php`

3.2 Joomla! / Joomla

  • En général, dans le template actuel, repérer <head> dans `index.php` et coller le script avant </head>
  • Ou utiliser une option backend/template permettant d’ajouter du code personnalisé/HTML personnalisé dans le head (selon le template)

3.3 Magento (approche d’injection frontend)

  • Si vous souhaitez seulement un blocage au niveau de l’affichage, vous pouvez ajouter le script dans le head global du thème (zone head du thème/layout)
  • Dans le backend Magento, on trouve souvent un réglage « HTML Head » ou similaire (selon version/thème) visant à injecter du contenu dans <head> sur tout le site

> Comme votre objectif est « éviter autant que possible le code côté serveur », on ne développe pas ici les approches XML/modules — si vous pouvez injecter le script dans le head, cela fonctionne.

3.4 Shopify

  • Online Store → Themes → Edit code
  • Trouver `theme.liquid` et coller le script avant </head> (sur tout le site)

3.5 Autres systèmes courants (schéma général)

  • Tout système qui permet d’éditer le <head> global : placer le script le plus tôt possible dans le head
  • Si vous ne voulez limiter que certaines pages : ajouter le script uniquement dans les templates des pages concernées

4) Résumé

Le cœur de cet article tient en une phrase :

> Le navigateur appelle https://my.ipin.io/info et obtient { country }. Si country === "TW", l’accès à la page est immédiatement terminé en affichant « accès refusé / 404 / erreur personnalisée », ce qui met en place une solution frontend de blocage par région IP.

C’est simple, rapide à mettre en place, et adapté aux « notices de conformité / notices d’indisponibilité régionale / filtrage frontend ». Dans beaucoup de cas, une « présentation d’erreur » côté frontend suffit déjà pour les besoins produit et opérationnels : l’utilisateur comprend clairement que la page n’est pas disponible dans sa région, et le contenu principal ainsi que les points d’entrée ne sont pas davantage exposés. Si vous avez besoin d’un contrôle plus strict « non contournable », vous pouvez ajouter une couche de blocage côté serveur ou CDN.

5) Questions fréquentes (FAQ)

Q1 : Le blocage frontend peut-il vraiment « interdire l’accès » ?

Plus précisément, il permet à la « couche de présentation » de renvoyer un résultat de refus clair (par exemple une page 403/404/message d’erreur), ce qui empêche les utilisateurs “normaux” de continuer à utiliser les fonctionnalités de la page. Cependant, des personnes expérimentées peuvent désactiver JavaScript ou modifier les scripts, donc ce n’est pas une barrière de sécurité non contournable. Si vous avez besoin d’un « blocage absolu », ajoutez une couche côté serveur ou CDN. Si votre objectif est un message de conformité et d’indisponibilité, le blocage frontend suffit généralement.

Q2 : Pourquoi placer le script dans le <head> et le plus tôt possible ?

Plus le contrôle est effectué tôt, plus vous pouvez terminer le flux d’accès avant le rendu du contenu principal, ce qui réduit les “flashs” où l’utilisateur voit brièvement la page avant blocage. Cela évite aussi de charger des ressources inutiles (scripts, images, composants). C’est l’emplacement le plus cohérent pour l’expérience utilisateur et la performance.

Q3 : Que faire si la requête vers https://my.ipin.io/info échoue ?

La stratégie la plus courante est « autoriser par défaut » afin qu’une panne d’API externe ne bloque pas par erreur des utilisateurs normaux. Une autre stratégie est « refuser par défaut », adaptée aux scénarios de conformité stricte. Vous pouvez choisir selon le risque métier. Dans tous les cas : garder la logique simple — si country est récupéré, on teste ; sinon, on applique la politique par défaut.

Q4 : Y aura-t-il des problèmes CORS ?

C’est possible. Si l’API n’autorise pas l’accès cross-origin depuis le navigateur, fetch sera bloqué. Dans ce cas, vous devez vous assurer que l’API active CORS (par exemple en autorisant votre domaine), sinon le navigateur ne pourra pas lire le JSON retourné. Pour une approche « frontend-first », CORS est le point principal à vérifier à l’avance.

Q5 : Quel est le standard pour la valeur country ?

Il s’agit généralement d’un code pays/région à deux lettres (souvent ISO 3166-1 alpha-2). TW en est un exemple typique. En pratique, il est conseillé d’utiliser systématiquement des codes en majuscules et de conserver la liste noire dans le même format pour éviter les erreurs liées à la casse.

Q6 : Comment passer de « bloquer un pays » à « n’autoriser que certains pays » ?

Il suffit d’inverser la logique :
- allowlist : n’autoriser que ["US","JP"], et pour tous les autres afficher « accès refusé/message d’erreur »
- blocklist : ne bloquer que ["TW"], et autoriser tous les autres
Les deux approches reviennent à vérifier si country appartient à une liste ; la différence se situe dans le comportement par défaut.