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コールバックでトークンを受け取る
構成イメージは次のようになります。
- ブラウザ(匿名ユーザー)が Web アプリにアクセス
- JavaScript が
/api/azure-maps/token(トークンブローカー API)にリクエスト - バックエンドは Managed Identity やアプリ登録を使って Azure Maps 用トークンを取得、短命トークンとしてブラウザへ返す
- ブラウザは受け取ったトークンを使って 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/403 | RBAC ロール不足、リージョン不整合、SAS のスコープ不備 | Data Reader 付与とリージョン確認、SAS の API 許可範囲を再確認 |
| 数分〜数十分後にだけ地図が落ちる | SAS 有効期限切れ、サーバー時刻のズレ | 有効期限を十分長く取り、更新タイミングを明示。NTP 同期 |
| ブラウザコンソールに CORS エラー | allowedOrigins の誤設定、プレフライトでヘッダーが許可されていない | オリジンの完全一致と、必要なヘッダー・メソッドの許可 |
CORS 設定の落とし穴
もっとも多いのが CORS 周りの取りこぼしです。allowedOrigins は 完全一致で判定されるため、次のような違いも別扱いになります。
http://localhost:3000とhttp://localhost:5173https://example.comとhttps://www.example.comhttp://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:3000http://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 を公開サイトに組み込む場合は、ぜひここで紹介した構成とチェックリストをベースに、自身のシステムに合わせてカスタマイズしてみてください。

コメント