IP 位置に基づいてアクセスを禁止する

著者:Lisa Farrell · 2026-01-18

IP 位置に基づくフロントエンド側のアクセス制御ソリューション:ページの読み込み時に https://my.ipin.io/info をリクエストして、訪問者の国コード(country)を取得します。country が TW(またはあなたが設定した他の地域)と判定された場合、ページは通常のコンテンツ提供を継続せず、フロントエンドでアクセス処理を直接終了します。

「地域によってアクセスを制限する」という要件の多くは、強固なセキュリティ(ハッカー対策のようなもの)を目的としているわけではなく、より一般的なフロントエンドの利用シーンに属します:

  • 一部の国/地域ではサービスを一時的に提供しない(コンプライアンス、著作権、決済、物流などの理由)
  • ユーザーがページに入ったタイミングで地域判定を行い、コンテンツ/ボタン/ダウンロードリンクを表示するかどうかを決めたい
  • キャンペーンが特定の地域のみ対象で、その他の地域には「サービス対象外」または「利用不可」を直接表示したい

この方法はフロントエンドに特に適しています:ブラウザが GET https://my.ipin.io/info を直接リクエストし、API は「リクエストを発行した IP」に基づいて位置情報を返します。例:

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

その後は `country === "TW"` を判定するだけで、「アクセス禁止」の処理を実行できます(例:アクセス禁止メッセージを表示する、404 文言を表示する、または独自のエラーページ構造で元ページを上書きする)。ここでの重要点は「リダイレクト」を議論するのではなく、アクセス結果をエラー/拒否として明確に提示し、ユーザーに「アクセス不可」の最終状態を見せることです。

1)ロジックと動作原理(2 段落)

第 1 段落:フロー
ユーザーがページを開く → ブラウザが直ちに https://my.ipin.io/info をリクエスト → `country` を取得 → ブラックリスト(例:`TW`)に一致したら、ページの後続表示を即座にブロック:通常のコンテンツをレンダリングせず、「アクセス禁止 / 404 / カスタムエラー」といった最終状態を直接出力する。一致しなければ、ページの機能とコンテンツを通常どおり読み込みます。

第 2 段落:なぜこの API にフロントエンドが適しているのか
ブラウザがサードパーティ API を直接呼び出すため、サードパーティが見るのは訪問者の「グローバル IP」であり、返される国/地域はユーザー本人に対応します。これはまさにあなたが欲しい効果です。

2)コード実装(できるだけシンプル、フロントエンド中心)

2.1 いちばん簡単:一致したら「アクセス禁止」(推奨)

このコードをページの <head> に入れてください(できるだけ前が良い)。一致したらページ内容を上書きし、明確な「アクセス禁止」メッセージを表示します。文言はあなたのコンプライアンス説明や提供範囲の説明に置き換え可能です。

例 A: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 アクセス禁止</h1>" +
        "<p>お住まいの地域ではサービスをご利用いただけません。</p>" +
        "</body>";
    }
  } catch (e) {
    // 取得できない場合はデフォルトで許可(デフォルトで拒否にも変更可能)
  }
})();
</script>

例 B:TW 以外をアクセス禁止(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 アクセス禁止</h1>" +
        "<p>このページは指定された地域のみ利用できます。</p>" +
        "</body>";
    }
  } catch (e) {
    // 取得できない場合はデフォルトで許可(デフォルトで拒否にも変更可能)
  }
})();
</script>

例 C:TWUS などをアクセス禁止(ブラックリスト)

<script>
(async function () {
  const blocked = ["TW", "US"]; // いずれかに一致したらアクセス禁止
  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 アクセス禁止</h1>" +
        "<p>お住まいの地域ではサービスをご利用いただけません。</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.2 404 Not Found を表示(「利用不可/存在しない」表現)

特定地域では「制限されています」と明示するより、「ページが存在しない」ように見せたい場合があります。その場合、404 の意味合いを持つエラーページ構造でコンテンツを上書きできます。注意:これはあくまでフロントエンドでの表示であり、サーバーが返す HTTP ステータスコードを実際に変更するものではありませんが、ページ体験としては十分に直感的です。

<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 ブラックリストを配列で管理(実務に近い)

実務では 1 地域だけをブロックするのではなく、ブラックリストを管理することが多いです。以下は最小構成のまま:一致したら即「アクセス禁止」を表示します。

<script>
(async function () {
  const blocked = ["TW"]; // ブロックする国/地域コード
  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 アクセス禁止</h1>" +
        "<p>お住まいの地域ではサービスをご利用いただけません。</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.4 React(最短で使える:一致したらエラーページを表示)

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 アクセス禁止</h1>
        <p>お住まいの地域ではサービスをご利用いただけません。</p>
      </div>
    );
  }

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

2.5 Vue(同じく最短:一致したらエラー表示)

<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 アクセス禁止</h1>
    <p>お住まいの地域ではサービスをご利用いただけません。</p>
  </div>

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

3)WordPress / Joomla / Magento / Shopify などでの実装(フロントエンド方式 + コード)

考え方はどれも同じです:「超シンプルな JS」をサイト全体の Head(またはレイアウトファイル)に挿入する。目的が「フロントエンド制御 + エラー表示」なので、スクリプトが十分早く実行されれば、メインのレンダリング前に地域判定を終え、一致時には「アクセス禁止/404/エラー表示」という最終状態をそのまま見せられます。

以下のスニペットを統一して使うのがおすすめです(最簡「アクセス禁止」版):

<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 アクセス禁止</h1>" +
        "<p>お住まいの地域ではサービスをご利用いただけません。</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

3.1 WordPress

  • 方法 A(最も簡単):ヘッダー/フッターにスクリプトを挿入できるプラグインで Header に貼り付け(全体適用)
  • 方法 B:テーマの `header.php`</head> 直前に貼り付け

3.2 Joomla! / Joomla

  • 通常はテンプレートの `index.php` 内の <head> を探し、</head> の直前に貼り付け
  • または Head にカスタムコード/HTML を入れられるテンプレート設定を利用(入口はテンプレートにより異なる)

3.3 Magento(フロントエンド注入の考え方)

  • 表示レイヤーでのブロックだけが目的なら、テーマのグローバル Head(theme/layout の head 領域)に追加
  • Magento の管理画面にも「HTML Head」相当の設定がある場合が多く(バージョン/テーマ依存)、目的は全体の <head> に挿入すること

> 目的が「サーバー側コードを極力避ける」なので、ここでは XML/モジュール方式は掘り下げません。Head に入れられれば動きます。

3.4 Shopify

  • Online Store → Themes → Edit code
  • `theme.liquid` を見つけて </head> の直前に貼り付け(全体適用)

3.5 その他の一般的なシステム(共通パターン)

  • グローバルな <head> を編集できるシステム:Head のできるだけ前に配置
  • 特定ページだけ制限したい:該当テンプレートにだけ追加

4)まとめ

この記事の要点は 1 文にまとめられます:

> ブラウザが https://my.ipin.io/info をリクエストして { country } を取得し、country === "TW" の場合は「アクセス禁止 / 404 / カスタムエラー」の表示でページアクセスを直接終了することで、IP 地域に基づくフロントエンドのアクセス禁止を実現する。

実装コストが低く、導入も速く、「コンプライアンス表示 / 地域的な利用不可表示 / フロントエンドの流量制御」に適しています。多くのビジネスでは、フロントエンドの「エラー表示」だけで十分に目的を達成できます。より厳密に「回避不能」にしたい場合は、サーバーや CDN 側で追加のブロックを重ねるとよいでしょう。

5)よくある質問(FAQ)

Q1:フロントエンドのブロックで本当に「アクセス禁止」できますか?

より正確には、表示レイヤーで明確な拒否結果(403/404/エラーメッセージ)を返し、通常のユーザーがページ機能を使い続けられないようにできます。ただし、JavaScript を無効化したりスクリプトを変更できる人には回避され得るため、回避不能なセキュリティ壁ではありません。「絶対に禁止」したい場合はサーバー/CDN 側のブロックを追加してください。コンプライアンスや利用不可の告知が目的なら、フロントエンドのみでも十分なことが多いです。

Q2:なぜスクリプトを <head> に、しかもできるだけ前に置くのですか?

早く判定できるほど、メインコンテンツが描画される前にアクセスを終了でき、「一瞬見えてからブロックされる」チラつきを減らせます。また不要なリソース(スクリプト、画像、コンポーネント)を読み込まずに済むため、UX と性能の両面で合理的です。

Q3:https://my.ipin.io/info のリクエストが失敗したらどうしますか?

最も一般的なのは「失敗時はデフォルトで許可」です。外部 API の障害で正常ユーザーを誤ってブロックしないためです。一方で「失敗時はデフォルトで拒否」は強いコンプライアンス用途に向きます。どちらを採用するかはビジネスリスク次第です。いずれにせよ、country が取れたら判定、取れなければデフォルト方針、というシンプルさが重要です。

Q4:CORS の問題は起きますか?

起きる可能性があります。API がブラウザからのクロスオリジンアクセスを許可していない場合、fetch はブラウザにブロックされます。この場合、API 側で CORS を有効化(例:あなたのドメインを許可)する必要があります。フロントエンド主体の方式では、CORS は事前に必ず確認すべきポイントです。

Q5:country の値はどの標準ですか?

一般的には 2 文字の国/地域コード(多くは ISO 3166-1 alpha-2)です。TW は典型例です。実運用では大文字で統一し、ブラックリストも同じ形式に揃えると、大小文字の違いによる判定ミスを避けられます。

Q6:「特定国をブロック」から「特定国だけ許可」に変えるには?

ロジックを反転するだけです:
- allowlist:["US","JP"] のみ許可し、それ以外は「アクセス禁止/エラー表示」
- blocklist:["TW"] のみブロックし、それ以外は許可
どちらも本質は country がリストに含まれるかどうかの判定で、違いはデフォルト挙動です。