公開WebサイトにおけるAzure Maps認証方式のベストプラクティス|SASトークンとEntra IDでsubscription key直書きを卒業する方法

Azure Maps を公開 Web サイトに組み込むとき、もっとも悩ましいのが「非ログイン(匿名)ユーザー向けにどう認証させるか」という点です。SAS トークンか subscription key 直書きか、あるいは Entra ID(旧 Azure AD)か。この記事では、実運用を前提にした推奨方式と、安定稼働させるための CORS・RBAC 設定、.NET 実装例までを具体的に整理します。

目次

Azure Maps を公開 Web サイトで使うときの基本方針

前提として、Azure Maps を 非ログインの公開 Web サイトで使う場合でも、ブラウザから Azure Maps のエンドポイントに直接アクセスしています。つまり、ブラウザに長期利用可能な鍵を置くと、その鍵は「誰でも使える共通鍵」になってしまいます。

そのため、公式ドキュメントや実運用のナレッジでは、次のような方針がほぼ共通認識になっています。

  • subscription key をブラウザに置かない
  • 短命トークン(SAS or Entra ID アクセストークン)をサーバーで発行し、フロントへ配布する
  • フロントエンドでは Azure Maps Web SDK の authType: "anonymous"+getToken コールバックでトークンを受け取る

構成イメージは次のようになります。

  1. ブラウザ(匿名ユーザー)が Web アプリにアクセス
  2. JavaScript が /api/azure-maps/token(トークンブローカー API)にリクエスト
  3. バックエンドは Managed Identity やアプリ登録を使って Azure Maps 用トークンを取得、短命トークンとしてブラウザへ返す
  4. ブラウザは受け取ったトークンを使って Azure Maps Web SDK を初期化し、地図を表示

このとき、Azure Maps アカウント側では、CORS 設定と RBAC 設定が非常に重要です。これらが不十分だと「SAS だとときどき地図が読み込めないが、subscription key 直書きだと動く」という状況が起きがちです。

観点推奨方針
クライアントへの鍵配布subscription key・クライアントシークレットは サーバーのみに保持し、ブラウザには短命トークンだけ配布する
認証方式公開サイトでは SAS トークン または Entra ID アクセストークン をバックエンドで生成し配布
Web SDK 設定authType: "anonymous" + getToken でトークンブローカーから受け取る
Azure Maps 側設定CORS(allowedOrigins) と RBAC(Data Reader 等) を正しく設定する

なぜ subscription key 直書きが NG なのか(ざっくりと結論)

  • ブラウザに埋め込んだ key は 誰でもコピー可能
  • コピーされた key で第三者が好き放題 API を叩けるため、課金インパクトが読めない
  • RBAC によるきめ細かい制御ができず、機能制限や利用状況の把握が困難
  • 流出後のローテーションが大変(全フロントのビルドやキャッシュを一斉更新)

つまり、subscription key をブラウザに置くのは、家の鍵を玄関に貼っておくようなものです。MVP 段階であっても、最初から「短命トークン配布」方式で設計しておくほうが、後々のリプレイスコストを抑えられます。

SAS トークン vs Entra ID(旧 Azure AD)アクセストークン

公開サイト向けに使われる代表的な認証方式は次の 2 つです。

  • SAS(Shared Access Signature)トークン方式
  • Entra ID(旧 Azure AD)アクセストークン方式

どちらもフロントエンドから見れば「トークンを受け取って Web SDK に渡す」だけですが、発行元や運用の考え方が少し異なります。

観点SAS トークンEntra ID アクセストークン
発行主体Azure Maps アカウントに対する 共有キー から生成Microsoft Entra ID(旧 Azure AD)で アプリケーション/マネージド ID に発行
有効期限任意(数分〜最大 24 時間など)。SAS 作成時に設定通常は約 1 時間程度。自動的にローテーションされる
権限の絞り込みAPI の種類(render/search/route 等)やプロトコル(https)で細かく制限可能RBAC ロール(Azure Maps Data Reader 等)に基づくアクセス制御
実装のしやすさ一度 SAS を作って Key Vault に入れておけば、配布専用 API は比較的シンプルEntra ID アプリ登録や Managed Identity の設定が必要だが、プラットフォーム標準
運用上のイメージ「この API に、ここまでの権限だけを、〇時間だけ許可する鍵」「このアプリ(or ID)が持つロールに応じて、Azure Maps にアクセスできる認可チケット」
公開サイトとの相性短命・機能限定トークンを配りやすく、匿名公開サイトと親和性が高いバックエンドが Entra ID をすでに使っている場合に統一しやすい

どちらを選んでも構いませんが、

  • Azure 全体で Entra ID をすでに統一的に使っている → Entra ID トークン配布
  • 「とりあえず Azure Maps だけをシンプルに守りたい」 → SAS トークン配布

という形で決めると整理しやすくなります。

Web SDK から見るとどちらも同じパターン

フロントエンドでは、SAS でも Entra ID でも、基本的に同じ書き方になります。

<div id="map" style="height:500px"></div>
<script type="module">
  import * as atlas from 'azure-maps-control';

  const map = new atlas.Map('map', {
    center: [139.767, 35.681],
    zoom: 12,
    authOptions: {
      authType: 'anonymous',
      clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // UAMI またはアプリ登録のクライアント ID
      getToken: function (resolve, reject) {
        fetch('/api/azure-maps/token', { credentials: 'include' })
          .then(r => r.ok ? r.text() : Promise.reject(r.statusText))
          .then(token => resolve(token))
          .catch(err => reject(err));
      }
    }
  });

  map.events.add('ready', () => {
    // レイヤー追加など
  });
</script>

/api/azure-maps/token が返しているのが「SAS か Entra ID アクセストークンか」はフロント側からは意識せずに済みます。

SAS 方式が不安定になる典型要因と対処

「SAS 方式だと、ときどき地図が読み込めない」という相談の多くは、SAS そのものの不具合ではなく、CORS 設定・時刻ズレ・RBAC 設定などの周辺設定ミスです。代表的なパターンと対処をまとめます。

症状典型原因対処
本番だけ地図が表示されないCORS に本番ドメインが登録されていないAzure Maps アカウントの allowedOrigins に本番ドメインを追加
localhost では動くが検証環境で 401/403RBAC ロール不足、リージョン不整合、SAS のスコープ不備Data Reader 付与とリージョン確認、SAS の API 許可範囲を再確認
数分〜数十分後にだけ地図が落ちるSAS 有効期限切れ、サーバー時刻のズレ有効期限を十分長く取り、更新タイミングを明示。NTP 同期
ブラウザコンソールに CORS エラーallowedOrigins の誤設定、プレフライトでヘッダーが許可されていないオリジンの完全一致と、必要なヘッダー・メソッドの許可

CORS 設定の落とし穴

もっとも多いのが CORS 周りの取りこぼしです。allowedOrigins は 完全一致で判定されるため、次のような違いも別扱いになります。

  • http://localhost:3000 と http://localhost:5173
  • https://example.com と https://www.example.com
  • http://127.0.0.1:3000 と http://localhost:3000

開発用と本番用で、最低でも以下のようなオリジンを登録しておきます。

  • 本番:https://yourapp.com
  • 検証:https://stg.yourapp.com(必要なら)
  • ローカル:http://localhost:3000 など、実際に使うポート番号付き

Azure CLI での設定例です。

az maps account update \
  --name <maps-account-name> \
  --resource-group <rg-name> \
  --set cors.allowedOrigins='["https://yourapp.com","http://localhost:3000"]' \
  --set cors.supportCredentials=true

トークン期限切れとサーバー時刻のズレ

SAS は「開始時刻〜終了時刻」の範囲でのみ有効です。サーバーの時計がずれていたり、有効期限が極端に短かったりすると、ブラウザから見ると「ときどきだけ失敗する」状態になりがちです。

  • サーバーは NTP で時刻同期させる
  • SAS の有効期限は「アクセスパターン+キャッシュ」を考慮し、少なくとも数十分〜数時間程度の余裕を持たせる
  • フロント側で地図初期化前に毎回トークンを取得するか、有効期限を見てリフレッシュする

RBAC・リソースプロバイダーの不足

Entra ID/Managed Identity を使う場合は、次のポイントを必ず確認します。

  • Azure Maps アカウントに対し、Azure Maps Data Reader など必要なロールが付与されているか
  • サブスクリプションに Microsoft.Maps、Microsoft.ManagedIdentity、Microsoft.KeyVault のリソースプロバイダーが登録されているか
  • Azure Maps アカウントのリージョンと、トークンを取得している側の設定が一致しているか

「subscription key だと動くが Entra ID だと 403」というケースでは、ほぼ RBAC かリソースプロバイダーの設定が原因です。

subscription key をブラウザに置くリスク

subscription key を JS の中に直書きしてしまうと、次のようなリスクが現実的に発生します。

リスク内容想定される影響
鍵の窃取開発者ツール、ソースコード、ログ、CDN キャッシュなどから容易に抜き取られる第三者が自分の環境から Azure Maps API を使い放題になる
課金インパクトAPI 呼び出しが制御不能になり、DoS 的なアクセスでも課金され続ける突発的な高額請求、コスト予測の破綻
利用制御不能RBAC で制御できず、アクセス元 IP やユーザー単位での制御も難しい機能制限ができないため、誤用・濫用の検知が遅れる
ローテーション困難一度ブラウザ配布してしまうと、キャッシュなどに残り続けるインシデント対応時に短時間で安全なローテーションを行うのが難しい

どうしても PoC や開発初期で subscription key を直書きしたくなる場面はありますが、本番はもちろん、可能なら PoC の段階から 短命トークン配布方式にしておくと移行がスムーズです。

localhost 開発での現実的な構成

Managed Identity は Azure 上でしか使えないため、ローカル開発環境ではそのまま利用できません。代わりに、次のようなパターンが現実的です。

パターン概要メリット注意点
DefaultAzureCredential(Azure CLI)開発者が az login したアカウントを使い、RBAC で Azure Maps への権限を付与追加シークレットが不要。ローカルと Azure 両方で同じコードを流用しやすい開発者アカウントに過剰権限を付けないよう注意
アプリ登録+クライアントシークレットEntra ID アプリを作成し、クライアント ID / シークレットでトークン取得サービスアカウント的に扱える。権限の最小化がしやすいシークレットの安全な管理(User Secrets や Key Vault)が必須
事前発行した SAS を Key Vault に格納ポータルや管理 API で SAS を作り、Key Vault から取得して配布コードがシンプル。SAS をローテーションしやすいSAS の有効期限と権限を適切に設計する必要がある
ステージングのトークンブローカー経由Azure Functions などにトークンブローカーをデプロイし、localhost からそこに問い合わせる本番に近い構成で検証できるステージング環境の管理コストが増える

いずれのパターンでも、Azure Maps アカウント側ではローカル開発用のオリジンを CORS に登録しておきます。

  • http://localhost:3000
  • http://localhost:5173

など、実際に使うポートを含めて指定することが重要です。

実装例:Azure Maps Web SDK と .NET バックエンド

ここからは、実際のコード例で構成イメージを具体化していきます。

フロントエンド(JavaScript / Azure Maps Web SDK)の例

最小構成のサンプルです。SAS/Entra どちらを使う場合でも同じ形で利用できます。

<div id="map" style="height:500px"></div>

<script type="module">
  import * as atlas from 'azure-maps-control';

  async function createMap() {
    const map = new atlas.Map('map', {
      center: [139.767, 35.681],
      zoom: 12,
      language: 'ja-JP',
      view: 'Auto',
      authOptions: {
        authType: 'anonymous',
        clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
        getToken: function (resolve, reject) {
          fetch('/api/azure-maps/token', {
            credentials: 'include',
            cache: 'no-store'
          })
            .then(r => r.ok ? r.text() : Promise.reject(r.statusText))
            .then(t => resolve(t))
            .catch(err => reject(err));
        }
      }
    });

    map.events.add('ready', () => {
      // 必要に応じてピンやレイヤーを追加
      // 例: 地図中心にシンボルを表示するなど
    });
  }

  createMap();
</script>

cache: 'no-store' を付けておくと、ブラウザがトークンを不要にキャッシュしてしまう可能性を減らせます。

.NET 8 Minimal API(Entra トークン配布)の例

次は、.NET 8 の Minimal API で /api/azure-maps/token を実装するサンプルです。ローカルでも Azure 上でも、そのまま動かしやすい構成を意識しています。

// Program.cs
using Azure.Core;
using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);

// CORS: 本番ドメイン+ローカルを明示
builder.Services.AddCors(options =>
{
    options.AddPolicy("Default", policy =>
    {
        policy
            .WithOrigins("https://yourapp.com", "http://localhost:3000")
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials();
    });
});

var app = builder.Build();

app.UseCors("Default");

// トークンブローカー API
app.MapGet("/api/azure-maps/token", async () =>
{
    // DefaultAzureCredential:
    // - Azure 上では Managed Identity
    // - ローカルでは AzureCliCredential / VisualStudioCredential などを自動検出
    var credential = new DefaultAzureCredential();

    // Azure Maps のスコープ
    var context = new TokenRequestContext(new[]
    {
        "https://atlas.microsoft.com/.default"
    });

    var token = await credential.GetTokenAsync(context);

    // トークン自体をプレーンテキストで返す(キャッシュさせないヘッダーを付ける)
    return Results.Text(token.Token, "text/plain", System.Text.Encoding.UTF8);
})
.WithName("GetAzureMapsToken")
.Produces(200, typeof(string));

app.Run();

この API を呼び出すプリンシパル(Managed Identity やアプリ登録、ローカルの開発者アカウント)には、Azure Maps Data Reader などのロールを付与しておきます。

ASP.NET Core MVC での実装例

MVC プロジェクトの場合も、基本は同じです。専用コントローラを用意してトークンを返します。

// Program.cs(.NET 8 のトップレベルステートメントの場合)

using Azure.Core;
using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

// Azure Maps 用の TokenCredential を DI で登録
builder.Services.AddSingleton<TokenCredential>(_ => new DefaultAzureCredential());

builder.Services.AddCors(options =>
{
    options.AddPolicy("Default", policy =>
    {
        policy
            .WithOrigins("https://yourapp.com", "http://localhost:3000")
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials();
    });
});

var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();
app.UseCors("Default");

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
// Controllers/AzureMapsTokenController.cs

using Azure.Core;
using Microsoft.AspNetCore.Mvc;

namespace YourApp.Controllers
{
    [Route("api/azure-maps")]
    [ApiController]
    public class AzureMapsTokenController : ControllerBase
    {
        private readonly TokenCredential _credential;

        public AzureMapsTokenController(TokenCredential credential)
        {
            _credential = credential;
        }

        [HttpGet("token")]
        public async Task<IActionResult> GetToken()
        {
            var context = new TokenRequestContext(new[]
            {
                "https://atlas.microsoft.com/.default"
            });

            var token = await _credential.GetTokenAsync(context);

            Response.Headers.CacheControl = "no-store";

            return Content(token.Token, "text/plain");
        }
    }
}

フロントエンドからは、Minimal API の例と同様に /api/azure-maps/token を叩くだけです。

SAS を Key Vault から配布する最小例

SAS 自体はポータルや管理 API で定期的に生成し、その値を Azure Key Vault に保存しておくパターンです。コード側では Key Vault から取り出すだけなのでシンプルです。

// Program.cs など

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton(sp =>
{
    var keyVaultUri = new Uri(builder.Configuration["KeyVaultUri"]!);
    return new SecretClient(keyVaultUri, new DefaultAzureCredential());
});

var app = builder.Build();

app.MapGet("/api/azure-maps/token", async (SecretClient secretClient) =>
{
    var sas = await secretClient.GetSecretAsync("AzureMapsSas");
    return Results.Text(sas.Value.Value, "text/plain");
});

app.Run();

SAS を発行するときは、

  • 利用 API(render/search/route 等)
  • 有効期限(最大 24 時間など)
  • 許可プロトコル(https のみ)

をできるだけ絞ることがポイントです。

Azure Maps アカウント側の CORS 設定とセキュリティ

トークンブローカーが正しく動いていても、Azure Maps アカウントの CORS 設定が誤っていると地図は表示されません。チェックポイントを整理します。

  • allowedOrigins に、本番・検証・ローカルすべてのオリジンを列挙する
  • origin は完全一致(スキーム・ドメイン・ポートまで)で指定する
  • 必要に応じて supportCredentials を有効化する

Azure CLI での設定例は前述の通りです。ポータルから設定する場合も、同じオリジンが登録されているかを確認します。

本番投入前チェックリスト

最後に、本番リリース前に最低限確認しておきたいポイントをチェックリスト形式でまとめます。

項目内容確認方法
CORS 設定本番ドメイン(https://yourapp.com 等)が allowedOrigins に登録されているAzure ポータルまたは az maps account show で確認
トークンブローカー/api/azure-maps/token が HTTPS で公開されており、
レスポンスに Cache-Control: no-store 相当が付いている
ブラウザの開発者ツールでレスポンスヘッダーを確認
RBAC 設定トークンを取得するプリンシパルに Azure Maps Data Reader 等の最小権限が付与されているポータルの「アクセス制御 (IAM)」でロールを確認
期限管理SAS / Entra トークンの有効期限と更新タイミングが設計されているコードレビューとテストで期限切れ時の挙動を確認
鍵管理subscription key はサーバー/Key Vault のみで使用し、ブラウザには一切配布していないフロントエンドのビルド成果物に key が含まれていないか検索
監視・アラート請求・リクエスト数のアラートと、401/403 や CORS エラーをログに残す仕組みがあるAzure Monitor とアプリケーションログを確認

まとめ:公開サイトで Azure Maps 認証を安全に運用するために

公開・匿名サイトに Azure Maps を組み込みたい場合、

  • subscription key 直書きは原則 NG
  • SAS トークンまたは Entra ID アクセストークンを、バックエンドで発行して配布する方式が推奨
  • フロントエンドは Web SDK の authType: "anonymous" + getToken でトークンを取得
  • Azure Maps アカウント側の CORS 設定 と RBAC(Azure Maps Data Reader 等) を正しく構成することが安定稼働の分水嶺
  • localhost 開発では Managed Identity の代わりに DefaultAzureCredential やアプリ登録、Key Vault に保存した SAS などを組み合わせる

最初から「トークンブローカー方式」で設計しておけば、MVP から本番まで同じアーキテクチャでスムーズにスケールできます。これから Azure Maps を公開サイトに組み込む場合は、ぜひここで紹介した構成とチェックリストをベースに、自身のシステムに合わせてカスタマイズしてみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次