AppServiceAuthSessionの期限切れ対策と/.auth/refresh 403の完全解説|Azure App Service Easy Auth×Microsoft Entra ID

「Easy Auth を使っているのに、1時間後に API が 401 / 403 を返し始める」「/.auth/me を叩いても古いクッキーとトークンしか返らない」――その原因と再発防止の具体策を、Azure App Service と Microsoft Entra ID(旧 Azure AD)の実運用を前提に、手順・設定・コード例・検証方法までまとめて解説します。プロキシ配下(Front Door / Application Gateway / WAF / CDN 等)でも破綻しない設計に落とし込みましょう。

目次

背景:Easy Auth(App Service Authentication)の動作と落とし穴

Easy Auth は Azure App Service に組み込まれた認証ゲートウェイで、アプリ変更なしで Microsoft Entra ID 等の IdP を使ったサインインを実現します。ユーザーがログインすると、ブラウザには AppServiceAuthSession(HttpOnly)などのクッキーが発行され、App Service の Token Store にはアクセストークン/ID トークン/(あれば)リフレッシュトークンが保存されます。アプリコードは X-MS-CLIENT-PRINCIPAL や X-MS-TOKEN-AAD-ACCESS-TOKEN といったヘッダー、もしくは /.auth/me で認証情報を参照できます。

主な内蔵エンドポイント

エンドポイント役割注意点
/.auth/login/aadEntra ID にリダイレクトしてログインpost_login_redirect_uri で復帰先を指定可能
/.auth/me現在のセッションとトークンの状態確認トークンの更新はしない(最新化は /.auth/refresh)
/.auth/refreshToken Store 内のトークンを更新し、新しい AppServiceAuthSession を Set-CookieGET で呼ぶ。クッキー同伴必須(credentials: 'include')
/.auth/logoutサインアウト(クッキー破棄)IdP 側セッションは別管理のため、完全ログアウト構成は別途検討

トークンとセッションの寿命(目安)

種別既定の有効期限延長可否備考
アクセストークン約 60 分不可(IdP ポリシー)期限切れ後はリフレッシュ経由で発行しなおす
ID トークン約 60 分不可クライアント情報の検証用途が主
リフレッシュトークン最長 90 日(条件あり)ポリシー依存offline_access スコープが必須
Easy Auth セッション最大 8 時間 + 猶予可token-refresh-extension-hours で延長(後述)

ここで重要なのは、/.auth/me はあくまで「照会」APIであり、アクセストークンの更新は行いません。更新は /.auth/refresh だけが担います。

症状:AppServiceAuthSession が期限切れになり、/.auth/me も古いまま

プロキシ配下の Web アプリで「1 時間後に API が 401/403 になり、/.auth/me を見ても失効済みのトークンしか入っていない」という相談が多く寄せられます。これは設計として正しく、更新処理を実装していないことが根本原因です。解決策はシンプルで、60 分ごと(あるいは期限–数分前)に /.auth/refresh をサイレント呼び出し、Token Store とクッキーを進行更新することです。

解決アクション①:/.auth/refresh を定期実行

GET で呼び出すと、バックエンド Token Store のアクセストークンが更新され、レスポンスの Set-Cookie で新しい AppServiceAuthSession が配布されます。その後に /.auth/me を呼び直すと、新しいアクセストークンが取得できます。

// SPA の例(fetch)
async function silentRefresh() {
  const res = await fetch('/.auth/refresh', {
    method: 'GET',
    credentials: 'include',   // ← 必須(クッキー同伴)
    cache: 'no-store',
  });
  if (!res.ok) {
    // 403/401/500 などはハンドリングしてリトライや再ログインへ
    throw new Error('refresh failed: ' + res.status);
  }
}

function scheduleRefresh({ intervalMinutes = 55 } = {}) {
// 60分のアクセストークン期限より少し短く
silentRefresh().catch(console.warn);
setInterval(() => silentRefresh().catch(console.warn), intervalMinutes * 60 * 1000);
}

// アプリ起動時にセットアップ
scheduleRefresh({ intervalMinutes: 55 });
// Axios インターセプターで「期限切れ→自動更新→再送」
// (API 401受信時に1回だけリフレッシュを試みてから再送)
import axios from 'axios';
let refreshing = null;

const api = axios.create({ withCredentials: true });

api.interceptors.response.use(undefined, async (error) => {
  const original = error.config;
  if (error.response && error.response.status === 401 && !original.__retried) {
    original.__retried = true;
    refreshing = refreshing || fetch('/.auth/refresh', { credentials: 'include' });
    try { await refreshing; } finally { refreshing = null; }
    return api(original);  // 再送
  }
  return Promise.reject(error);
});
# 手元検証(curl)
curl -i -X GET "https://<app-name>.azurewebsites.net/.auth/refresh" \
  --cookie "AppServiceAuthSession=<existing>"

解決アクション②:リフレッシュトークンの前提条件を満たす

  • offline_access スコープ:アプリ登録(Microsoft Entra ID)で offline_access を要求しておくと、条件を満たすログインでリフレッシュトークンが交付されます。
  • Token Store を有効化:App Service の認証設定で Token Store を On に。これにより Easy Auth がトークンを安全に保管・更新できます。

Auth 設定(v2)例:loginParameters と Token Store

{
  "platform": { "enabled": true },
  "globalValidation": {
    "requireAuthentication": true,
    "unauthenticatedClientAction": "RedirectToLoginPage"
  },
  "identityProviders": {
    "azureActiveDirectory": {
      "enabled": true,
      "registration": {
        "clientId": "<APP_CLIENT_ID>",
        "openIdIssuer": "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
      },
      "login": {
        "loginParameters": [
          "scope=openid profile offline_access"
        ]
      }
    }
  },
  "login": {
    "tokenStore": { "enabled": true }
  }
}

解決アクション③:セッション猶予を延ばす(運用緩和)

夜間バッチや長時間ワークフローで再サインインを抑制したい場合、次の CLI で Easy Auth のセッション延長を設定できます。

az webapp auth update \
  --resource-group <RG> \
  --name <APP_NAME> \
  --token-refresh-extension-hours 72

ただし、セキュリティ要件とトレードオフです。安易に上げ過ぎず、定期的な /.auth/refresh の実装が基本である点は変わりません。

確認ポイント:/.auth/me の正しい使い方

/.auth/me は照会専用です。更新後に呼ぶことで「新しいアクセストークンが入っているか」を検証できます。更新は必ず /.auth/refresh 経由で行います。

更新のフロー図(簡略)

[Browser] -- GET /.auth/refresh (Cookie 同伴)
     |        <-- 200 + Set-Cookie: AppServiceAuthSession=NEW
     |
  (Token Store が新しい access_token / id_token を保持)
     |
  [Browser] -- GET /.auth/me --> { ... "access_token": "NEW", ... }

症状:/.auth/refresh が 403 Forbidden になる

2025 年前後から、構成の組み合わせによって /.auth/refresh が 403 を返す事例が散見されます。多くは「構成の不整合」か「同意の状態」、もしくは「Cookie の属性改変(プロキシ)」に起因します。以下のチェックリストで一気に切り分けましょう。

A. 設定の不整合(Client ID / Audience / API 権限)

  • App Service 側の Client ID:認証ブレードの「Microsoft(Entra ID)」設定で、App Registration の Application (client) ID と一致しているか。
  • 許可されたトークン受信者(Allowed Token Audiences):バックエンド API の Application ID URI(例:api://<clientId>)を正しく登録。
  • Expose an API → Authorized client applications:SPA / フロントエンドの Client ID を追加し、バックエンド API にアクセスできるよう明示。
  • スコープ定義と付与:フロントから要求するスコープ(例:api://.../.default や個別スコープ)が API 側に存在し、ユーザー/アプリに許可されているか。

B. スコープ/同意の再取得(consent)

ユーザーまたはテナントの同意状態が変わると、リフレッシュトークンの再発行が必要になる場合があります。ログイン URL または /.auth/refresh を呼び出す前に、prompt=consent を一度付与して同意を取り直すと改善する事例があります。

https://<app>.azurewebsites.net/.auth/login/aad?prompt=consent&post_login_redirect_uri=https%3A%2F%2F<app>.azurewebsites.net%2F

C. Cookie の SameSite 属性とプロキシの書き換え

Front Door / Application Gateway / Nginx / CDN 等の配下では、Set-Cookie の属性が意図せず改変・削除されることがあります。AppServiceAuthSession はクロスサイトでの利用ケースがあり、SameSite=None; Secure の維持が必要です。ブラウザの開発者ツールで以下を必ず確認してください。

  • レスポンスに Set-Cookie: AppServiceAuthSession=...; Path=/; HttpOnly; SameSite=None; Secure が残っている
  • プロキシで「Cookie 書き換え」「ヘッダー正規化」「HTTPS→HTTP ダウングレード」等が無効化されている
  • バックエンド到達時も Host ヘッダーが期待どおりで、クッキードメインとの齟齬がない

ワークアラウンドとリセット

  • Token Store のオフ→オン:構成変更後、一度 Token Store を Off にしてから On に戻すと、内部キャッシュがクリアされ改善することがあります。
  • ブラウザクッキーの削除→再ログイン:ユーザー作業になりますが、403 の間は一時回避策として有効です。

403 切り分け早見表

現象疑うポイント対処
/.auth/refresh 即 403Client ID 不一致 / Audience 未設定認証設定の Client ID と App 登録の整合/Allowed Token Audiences を見直し
初回 OK、数日後から 403同意の失効・スコープ変更prompt=consent で同意取り直し、スコープ再確認
プロキシ配下のみ 403Cookie 属性改変 / HTTPS 統一不備SameSite=None; Secure を保持、HTTP を通さない、ヘッダー書き換えを無効化

実装レシピ:SPA / モバイル / サーバー間でのベストプラクティス

SPA(React / Vue / Angular 等)

  • API コールは withCredentials(Axios)または credentials: 'include'(fetch)を必ず指定。
  • アプリ起動時に silent refresh をスケジュール。期限が近づく前(例:55 分)に先回りで更新。
  • 401 を受信したら 1 回だけ /.auth/refresh → 失敗時はログインページへ。

React のフック例

import { useEffect } from 'react';

export function useEasyAuthRefresh(intervalMinutes = 55) {
useEffect(() => {
let timer = null;
const run = async () => {
try {
await fetch('/.auth/refresh', { credentials: 'include', cache: 'no-store' });
} catch (e) {
console.warn('refresh error', e);
}
};
run(); // 初回
timer = setInterval(run, intervalMinutes * 60 * 1000);
return () => clearInterval(timer);
}, [intervalMinutes]);
}

モバイル(WebView / Capacitor / MAUI Blazor 等)

  • WebView 側で /.auth/refresh をバックグラウンド GET。クッキー共有(SameSite=None; Secure)と Third-Party Cookies の扱いに注意。
  • アプリ復帰(フォアグラウンド)時に即時リフレッシュするハンドラを追加。

サーバー間(App Service <-> 下流 API)

Easy Auth が Token Store のアクセストークンを更新していれば、アプリは X-MS-TOKEN-AAD-ACCESS-TOKEN を読み取り、下流 API に転送できます。

// Node.js(Express)で下流 API にプロキシする例
app.get('/api/data', async (req, res) => {
  const accessToken = req.header('X-MS-TOKEN-AAD-ACCESS-TOKEN');
  if (!accessToken) return res.status(401).send('no token');

const apiRes = await fetch('[https://downstream.example.com/data](https://downstream.example.com/data)', {
headers: { Authorization: `Bearer ${accessToken}` }
});
res.status(apiRes.status).send(await apiRes.text());
});

この方式ではフロントがトークンを保持しないため、XSS リスクを抑えられます(推奨)。

プロキシ配下の設計チェックリスト(Front Door / AppGW / Nginx / CDN)

項目良い設定悪い設定/NG備考
プロトコル終端~バックエンドまで HTTPS途中で HTTP にダウングレードSecure クッキー無効化や混在コンテンツの原因
Cookie 書き換え無効(透過転送)Name/Path/SameSite を書き換えSameSite=None; Secure を維持
ヘッダー正規化Set-Cookie を削らないセキュリティ製品が Set-Cookie を除去WAF ルール除外を検討
Host ヘッダーフロントの FQDN を保持内部ホスト名に置換クッキー Domain/Path と齟齬が出る
CORS正しい Origin を許可、資格情報許可* + 資格情報フロント/バックのドメインが異なる場合必須

運用:ログ・監視・検証のすすめ

ブラウザ検証

  • Network タブで /.auth/refresh のレスポンスに Set-Cookie: AppServiceAuthSession=... があるか。
  • Cookie タブで AppServiceAuthSession の属性(HttpOnly / Secure / SameSite=None)が正しいか。
  • /.auth/me を更新後に呼び、expires_on などのクレームが延びているか。

サーバー側ログ

  • App Service の診断ログ(Authentication / Authorization)を有効化。
  • Application Insights で /.auth/refresh の 4xx 率、401 レスポンスの急増をアラート。

疑似ユーザー監視

1 時間+数分おきに /.auth/refresh → /.auth/me を実行する可用性テストを作成し、Set-Cookie の有無をヘルスチェックするのが実運用では効果的です。

セキュリティとコンプライアンスの注意点

  • フロントでアクセストークンをローカルストレージに保存しない(XSS リスク)。トークンは Token Store + HttpOnly クッキーで扱う。
  • リフレッシュ間隔は短くしすぎない(過剰なトラフィック)。期限–5 分などのバッファを推奨。
  • 長期セッション延長はユーザー行動とポリシーに応じて最小限に。MFA 再認証の要件がある場合は、セッション延長よりもフロー設計を見直す。
  • 複数ドメイン(app.example.com と api.example.com)を跨ぐ場合は、クッキーの Domain 設計と CORS を慎重に。

実践まとめ(要点)

  1. 更新 API は /.auth/refresh 一択。/.auth/me は状態確認専用。
  2. offline_access + Token Store を必ず有効にし、60 分ごとにサイレント更新。
  3. 403 は構成ミス・同意の失効・Cookie 改変が中心。チェックリストで早期是正。
  4. プロキシ配下では SameSite=None; Secure を壊さない。HTTPS 統一とヘッダー透過転送。
  5. 運用では「定期リフレッシュ・可用性監視・診断ログ」をセットにし、手動クッキー削除に頼らない設計に。

付録:トラブルシューティングの決定木(文字版)

症状: API が 401/403 / .auth/me が古い
 ├─ 実装: /.auth/refresh を定期実行しているか?
 │    ├─ No → 実装する(55分間隔など)
 │    └─ Yes
 │        ├─ クッキー同伴(credentials: 'include')しているか?
 │        └─ レスポンスに Set-Cookie があるか?
 ├─ 403 の場合
 │    ├─ Client ID / Audience 整合性を確認(App Service & App Reg)
 │    ├─ Authorized client applications にフロントの Client ID を追加
 │    ├─ 一度 prompt=consent で同意を再取得
 │    └─ プロキシで SameSite=None; Secure が維持されているか
 └─ 依然不可
      ├─ Token Store を Off→On でリセット
      ├─ ブラウザクッキー削除→再ログイン
      └─ 診断ログで 4xx の詳細・ルート原因を特定

付録:構成コマンドと設定サンプル集

Token Store とセッション延長(CLI)

# Token Store を有効化
az webapp auth update \
  --resource-group <RG> \
  --name <APP_NAME> \
  --token-store true

# セッション延長(例:72 時間)

az webapp auth update 
--resource-group  
--name  
--token-refresh-extension-hours 72

フロントエンド(ドメインが分離される場合)の CORS 設定

  • 許可する Origin にフロントの FQDN を追加。
  • 資格情報(クッキー)を使う場合は ワイルドカード(*)を使わない。

リフレッシュの検証スクリプト(Node.js)

import fetch from 'node-fetch';

async function verify() {
const refreshRes = await fetch('https://.azurewebsites.net/.auth/refresh', {
headers: { cookie: 'AppServiceAuthSession=<...>' }
});
console.log('refresh', refreshRes.status, refreshRes.headers.get('set-cookie'));

const meRes = await fetch('https://.azurewebsites.net/.auth/me', {
headers: { cookie: refreshRes.headers.get('set-cookie') }
});
const body = await meRes.json();
console.log('me', meRes.status, body);
}

verify().catch(console.error);

よくある質問(FAQ)

Q. なぜ /.auth/me が自動更新してくれないの?

A. /.auth/me は「現在の状態を返す」だけのサイドエフェクトなし API です。トークン更新は /.auth/refresh の責務です。

Q. どのくらいの間隔で /.auth/refresh すべき?

A. アクセストークンが約 60 分で失効する前提で、55 分など少し短い間隔を推奨します。スパイクを避けたい場合は API 呼び出し前の遅延リフレッシュ(インターセプター)と併用するのが実戦的です。

Q. 403 がどうしても消えないときは?

A. Client ID と Audience の整合/同意の再取得/Cookie 属性の維持が核心です。変更後は Token Store の Off→On でキャッシュをリセットし、最後の手段としてブラウザクッキー削除→再ログインで復旧させます。

Q. B2C でも同様?

A. 基本の考え方は同じです。プロバイダー名やポリシー(ユーザーフロー)の違いに留意してください。

結論

結論は 3 つです。① トークン更新は /.auth/refresh を使う(/.auth/me は照会専用)。② 403 の多くは構成ミスと同意の失効、またはプロキシによる Cookie 改変。③ プロキシ配下では SameSite=None; Secure の維持と HTTPS 統一を最優先。これらを実践すれば、ユーザーに手動のクッキー削除を求めることなく、シームレスで安全なセッション延長が実現できます。

この記事を書いた人

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

コメント

コメントする

目次