Azure Static Web Apps
Azure Static Web Appsは、GitHubやAzure DevOpsからコードを自動的にビルドし、Azureにデプロイするフルマネージドサービスです。静的コンテンツをグローバルに配信し、Azure FunctionsによるサーバーレスAPIをシームレスに統合できます。
概要
Azure Static Web Apps とは
Azure Static Web Appsは、モダンなWebアプリケーション開発のためのホスティングサービスで、以下の特徴を持ちます:
- 自動ビルド・デプロイ: GitHubまたはAzure DevOpsと統合し、プッシュ時に自動的にビルド・デプロイ
- グローバルCDN: 静的コンテンツを世界中のエッジロケーションから配信
- 統合API: Azure Functionsを使ったサーバーレスAPIの自動統合
- 組み込み認証: 複数の認証プロバイダーを設定なしで利用可能
- カスタムドメイン・SSL: 無料のSSL証明書とカスタムドメインのサポート
- プレビュー環境: プルリクエストごとに自動的にステージング環境を作成
アーキテクチャ
┌─────────────────────────────────────────────────────┐
│ GitHub / Azure DevOps │
│ (ソースコード) │
└──────────────────┬──────────────────────────────────┘
│ Push/PR
▼
┌─────────────────────────────────────────────────────┐
│ GitHub Actions / Azure Pipelines │
│ (自動ビルド・デプロイ) │
└──────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Azure Static Web Apps │
│ ┌──────────────────┐ ┌───────────────────┐ │
│ │ 静的コンテンツ │ │ Azure Functions │ │
│ │ (HTML/JS/CSS) │ │ (API) │ │
│ └──────────────────┘ └───────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ 認証・認可 │ │
│ │ (GitHub, Azure AD, Twitter, Google等) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
▼
ユーザー (世界中のエッジから配信)
主な機能
1. 静的コンテンツのホスティング
- HTML、CSS、JavaScript、画像などの静的ファイルをホスト
- React、Angular、Vue.js、Blazor WebAssemblyなどのSPA(Single Page Application)に対応
- Next.js、Gatsby、Hugoなどの静的サイトジェネレーターをサポート
2. サーバーレスAPI統合
/apiパスで Azure Functions を自動的に統合- Node.js、Python、.NET、Javaなどをサポート
- 静的コンテンツと同じドメインで提供されるため、CORSの問題が解消
3. 組み込み認証
以下の認証プロバイダーがデフォルトで利用可能:
- Azure Active Directory (AAD)
- GitHub
認証エンドポイント:
/.auth/login/<provider>: ログイン/.auth/logout: ログアウト/.auth/me: ユーザー情報取得
4. カスタムドメインとSSL
- 無料のSSL証明書(Let's Encrypt)を自動発行
- カスタムドメインの設定が簡単
- Apexドメインとサブドメインの両方をサポート
料金プラン
| 機能 | Free プラン | Standard プラン |
|---|---|---|
| 帯域幅 | 100 GB/月 | 100 GB/月 + 超過分課金 |
| ストレージ | 0.5 GB | 2 GB |
| カスタムドメイン | 2個 | 5個 |
| ステージング環境 | 3個 | 10個 |
| Azure Functions | 無料枠のみ | 専用プラン可能 |
| SLA | なし | 99.95% |
| 認証ユーザー数 | 無制限 | 無制限 |
Azure Functionsを使ったカスタムロール実装
Azure Static Web Appsでは、Azure Functionsを使ってカスタムの認証ロジックやロール割り当てを実装できます。
カスタム認証の仕組み
- ユーザーが認証プロバイダーでログイン
/.auth/login/<provider>/callbackにリダイレクト- Azure Functions で
/api/auth/rolesまたは カスタムエンドポイントを実装 - ユーザー情報に基づいてカスタムロールを返却
- 以降のリクエストでロール情報が利用可能
カスタムロールAPIの実装例 (Node.js)
プロジェクトディレクトリに api フォルダを作成し、以下のようなAzure Functionを実装します。
api/GetUserRoles/index.js
module.exports = async function (context, req) {
// 認証されたユーザー情報を取得
const userInfo = req.headers['x-ms-client-principal'];
if (!userInfo) {
context.res = {
status: 401,
body: { error: "認証が必要です" }
};
return;
}
// Base64デコード
const decoded = Buffer.from(userInfo, 'base64').toString('utf-8');
const user = JSON.parse(decoded);
// ユーザー情報に基づいてロールを決定
const roles = determineUserRoles(user);
context.res = {
status: 200,
body: {
userId: user.userId,
userDetails: user.userDetails,
roles: roles
}
};
};
function determineUserRoles(user) {
const roles = ['authenticated'];
// メールアドレスに基づいてロールを割り当て
if (user.userDetails && user.userDetails.endsWith('@company.com')) {
roles.push('employee');
}
// 特定のユーザーIDに管理者ロールを付与
const adminIds = [
'github|123456',
'aad|abcd-1234-efgh-5678'
];
if (adminIds.includes(user.userId)) {
roles.push('admin');
}
// データベースからロール情報を取得する例
// const dbRoles = await fetchRolesFromDatabase(user.userId);
// roles.push(...dbRoles);
return roles;
}
api/GetUserRoles/function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get", "post"],
"route": "auth/roles"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
データベースと連携したロール管理
実際のアプリケーションでは、ロール情報をデータベースで管理することが一般的です。
const { CosmosClient } = require('@azure/cosmos');
// Cosmos DBクライアントの初期化
const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING);
const database = client.database('UsersDB');
const container = database.container('UserRoles');
async function getUserRolesFromDatabase(userId) {
try {
const { resource } = await container
.item(userId, userId)
.read();
return resource ? resource.roles : ['authenticated'];
} catch (error) {
console.error('ロール取得エラー:', error);
return ['authenticated']; // デフォルトロール
}
}
module.exports = async function (context, req) {
const userInfo = req.headers['x-ms-client-principal'];
if (!userInfo) {
context.res = { status: 401 };
return;
}
const decoded = Buffer.from(userInfo, 'base64').toString('utf-8');
const user = JSON.parse(decoded);
// データベースからロールを取得
const roles = await getUserRolesFromDatabase(user.userId);
context.res = {
status: 200,
body: {
userId: user.userId,
roles: roles
}
};
};
staticwebapp.config.json による認証設定
staticwebapp.config.json ファイルをプロジェクトのルートに配置することで、ルーティング、認証、認可を詳細に設定できます。
基本的な設定ファイル
{
"routes": [
{
"route": "/admin/*",
"allowedRoles": ["admin"]
},
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
},
{
"route": "/public/*",
"allowedRoles": ["anonymous"]
}
],
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/images/*", "/css/*", "/api/*"]
},
"responseOverrides": {
"401": {
"rewrite": "/login.html",
"statusCode": 302
},
"403": {
"rewrite": "/forbidden.html"
},
"404": {
"rewrite": "/404.html"
}
}
}
主要な設定項目
1. ルートとロールベースのアクセス制御
{
"routes": [
{
"route": "/admin/*",
"allowedRoles": ["admin"],
"statusCode": 403
},
{
"route": "/profile",
"allowedRoles": ["authenticated"]
},
{
"route": "/login",
"allowedRoles": ["anonymous"]
}
]
}
route: アクセス制御を適用するパス(ワイルドカード*使用可能)allowedRoles: アクセスを許可するロール(配列)"anonymous": 未認証ユーザーを含むすべてのユーザー"authenticated": 認証済みユーザーすべて- カスタムロール:
"admin","editor","employee"など
statusCode: アクセス拒否時のHTTPステータスコード(オプション)
2. 認証プロバイダーのカスタマイズ
{
"auth": {
"identityProviders": {
"azureActiveDirectory": {
"registration": {
"openIdIssuer": "https://login.microsoftonline.com/<tenant-id>/v2.0",
"clientIdSettingName": "AAD_CLIENT_ID",
"clientSecretSettingName": "AAD_CLIENT_SECRET"
},
"login": {
"loginParameters": ["scope=openid profile email"]
}
},
"customOpenIdConnectProviders": {
"myCustomProvider": {
"registration": {
"clientIdSettingName": "CUSTOM_CLIENT_ID",
"clientCredential": {
"clientSecretSettingName": "CUSTOM_CLIENT_SECRET"
},
"openIdConnectConfiguration": {
"wellKnownOpenIdConfiguration": "https://provider.com/.well-known/openid-configuration"
}
},
"login": {
"nameClaimType": "name",
"scopes": ["openid", "profile", "email"]
}
}
}
}
}
}
3. リダイレクトルール
{
"routes": [
{
"route": "/old-page",
"redirect": "/new-page",
"statusCode": 301
},
{
"route": "/external",
"redirect": "https://example.com",
"statusCode": 302
}
]
}
4. ヘッダーのカスタマイズ
{
"globalHeaders": {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Content-Security-Policy": "default-src 'self'"
},
"routes": [
{
"route": "/api/*",
"headers": {
"Cache-Control": "no-cache, no-store, must-revalidate",
"X-Custom-Header": "Custom-Value"
}
}
]
}
5. MIMEタイプの設定
{
"mimeTypes": {
".json": "application/json",
".wasm": "application/wasm",
".csv": "text/csv"
}
}
実践的な設定例
マルチテナントアプリケーション
{
"routes": [
{
"route": "/admin/*",
"allowedRoles": ["admin", "superadmin"]
},
{
"route": "/tenant/*/dashboard",
"allowedRoles": ["authenticated"]
},
{
"route": "/api/tenant/*",
"allowedRoles": ["tenant-user", "tenant-admin"]
}
],
"navigationFallback": {
"rewrite": "/index.html"
},
"responseOverrides": {
"401": {
"redirect": "/.auth/login/aad",
"statusCode": 302
},
"403": {
"rewrite": "/access-denied.html"
}
},
"globalHeaders": {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "SAMEORIGIN",
"Strict-Transport-Security": "max-age=31536000"
}
}
SPA(Single Page Application)用の設定
{
"routes": [
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
},
{
"route": "/admin",
"allowedRoles": ["admin"],
"rewrite": "/index.html"
}
],
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/assets/*", "/images/*", "/*.{css,js,json,ico,png,jpg,svg}"]
},
"responseOverrides": {
"401": {
"redirect": "/.auth/login/github",
"statusCode": 302
}
}
}
ベストプラクティス
1. セキュリティ
- 最小権限の原則: ロールには必要最小限の権限のみを付与
- 機密情報の保護: APIキーや接続文字列は環境変数を使用
- HTTPSの強制: すべての通信でHTTPSを使用(デフォルトで有効)
- セキュリティヘッダー: CSP、X-Frame-Optionsなどを適切に設定
{
"globalHeaders": {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
}
}
2. パフォーマンス
- キャッシュ戦略: 静的アセットに適切なキャッシュヘッダーを設定
- ファイルサイズの最適化: 画像やJavaScriptの圧縮・最適化
- CDNの活用: Azure Static Web Appsは自動的にグローバルCDNを使用
- APIの効率化: 不要なデータ転送を避け、レスポンスを最小化
{
"routes": [
{
"route": "/assets/*",
"headers": {
"Cache-Control": "public, max-age=31536000, immutable"
}
},
{
"route": "/api/*",
"headers": {
"Cache-Control": "no-cache, no-store, must-revalidate"
}
}
]
}
3. デプロイとCI/CD
- 環境変数の管理: 環境ごとに異なる設定はAzure Portalで管理
- ステージング環境の活用: PRごとに自動作成されるプレビュー環境でテスト
- 段階的ロールアウト: 本番環境への変更は段階的に適用
- ロールバック計画: 問題発生時の迅速なロールバック手順を準備
4. 監視とログ
- Application Insights: アプリケーションのパフォーマンスとエラーを監視
- ログ記録: Azure Functions内で適切なログを記録
- アラート設定: 異常なトラフィックやエラー率の上昇を検知
// Azure Functions内でのログ記録例
const appInsights = require('applicationinsights');
appInsights.setup(process.env.APPINSIGHTS_INSTRUMENTATIONKEY);
const client = appInsights.defaultClient;
module.exports = async function (context, req) {
try {
// 処理
client.trackEvent({
name: 'UserRoleAssigned',
properties: { userId: user.userId, role: 'admin' }
});
} catch (error) {
client.trackException({ exception: error });
context.log.error('エラー発生:', error);
}
};
トラブルシューティング
よくある問題と解決方法
1. 認証後にリダイレクトされない
原因: post_login_redirect_uri の設定が不適切
解決策:
// ログインリンクに正しいリダイレクトURIを指定
<a href="/.auth/login/github?post_login_redirect_uri=/dashboard">
GitHubでログイン
</a>
2. APIへのアクセスが403エラー
原因: allowedRoles の設定が適切でない
解決策: staticwebapp.config.json を確認
{
"routes": [
{
"route": "/api/*",
"allowedRoles": ["authenticated"] // anonymousから変更
}
]
}
3. カスタムロールが反映されない
原因: ロール割り当てAPIが正しく呼び出されていない
解決策:
- ブラウザの開発者ツールでネットワークタブを確認
/.auth/meエンドポイントで現在のロール情報を確認- Azure Functionsのログを確認してエラーをチェック
# Azure CLIでログを確認
az staticwebapp functions logs show --name <app-name> --resource-group <rg-name>
4. ローカル開発での認証テスト
解決策: Azure Static Web Apps CLI を使用
# インストール
npm install -g @azure/static-web-apps-cli
# ローカルで実行
swa start ./build --api-location ./api
# 認証エミュレーション付きで実行
swa start ./build --api-location ./api --app-devserver-url=http://localhost:3000
デバッグ手法
-
ユーザー情報の確認
// フロントエンドで現在のユーザー情報を取得fetch('/.auth/me').then(res => res.json()).then(data => console.log('User info:', data)); -
ロール情報の確認
// Azure Function内でユーザー情報をログ出力const userInfo = req.headers['x-ms-client-principal'];const decoded = Buffer.from(userInfo, 'base64').toString('utf-8');context.log('User details:', JSON.parse(decoded)); -
設定ファイルの検証
- Azure PortalでStatic Web Appの「Configuration」を確認
- デプロイログでビルドエラーをチェック
- GitHub Actionsのログを確認
まとめ
Azure Static Web Appsは、モダンなWebアプリケーション開発に最適なプラットフォームです:
- シンプルなデプロイ: GitHubと統合した自動デプロイ
- スケーラブル: グローバルCDNによる高速配信
- セキュア: 組み込み認証とカスタムロールによる柔軟なアクセス制御
- コスト効率: 無料プランでも充実した機能
- 開発者体験: ローカル開発からプレビュー環境まで充実したツールチェーン
staticwebapp.config.json とAzure Functionsを組み合わせることで、エンタープライズレベルの認証・認可システムを構築できます。