次の方法で共有


チュートリアル: TypeScript を使用して Azure Storage BLOB にイメージをアップロードする

このチュートリアルでは、資格情報を公開せずに、ブラウザーから Azure Blob Storage に直接ファイルをアップロードする方法について説明します。 TypeScript を使用して、Shared Access Signature (SAS) トークンとマネージド ID を使用して バレット キー パターン を実装し、セキュリティで保護されたキーレス認証を行います。

サンプル アプリケーションには次のものが含まれます。

  • 時間制限付き SAS トークンを生成する Fastify API
  • Azure Storage にファイルを直接アップロードする React フロントエンド
  • Azure Developer CLI を使用したデプロイのコードとしてのインフラストラクチャ

このチュートリアルを終了すると、Azure Container Apps に実際にデプロイされたアプリケーションが作成されます。これにより、ストレージ資格情報をブラウザーに公開することなく、セキュリティで保護されたファイルのアップロードが示されます。

前提 条件

開始する前に、次のことを確認します。

ヒント

このチュートリアルでは、ブラウザーで事前に構成された開発環境を提供する GitHub Codespaces を使用します。 ローカル セットアップは必要ありません。

Architecture

アップロード フローを示す Azure アーキテクチャ図: ユーザーは Web アプリ フロントエンドでファイルを選択し、フロントエンドは API アプリ バックエンドから SAS トークンを要求し、バックエンドはマネージド ID からユーザー委任キーを取得し、ストレージ BLOB コンテナーから SAS トークンを生成し、フロントエンドは SAS トークンを使用してファイルを Storage に直接アップロードし、バックエンド クエリストレージはアップロードされたファイルを一覧表示します。Container Registry は、両方のアプリのコンテナー イメージを提供します。

フロントエンドは API から SAS トークンを要求し、ファイルを Azure Storage に直接アップロードします。 アップロード後、API は、読み取り専用の SAS トークンを使用して、アップロードされたすべてのファイルを一覧表示して表示します。

[ファイルの選択] ボタンとコンテナー名のアップロードが表示された [Upload file to Azure Storage]\(ファイルを Azure Storage にアップロードする\) というタイトルの Web アプリのスクリーンショット。

重要な概念

ユーザー委任 SAS トークン

アプリケーションは、セキュリティで保護されたキーレス認証にユーザー委任 SAS トークンを使用します。 これらのトークンは、マネージド ID を介して Microsoft Entra ID 資格情報で署名されます。 この API は、特定のアクセス許可 (読み取り、書き込み、または削除) を使用して有効期間の短いトークン (10 ~ 60 分) を生成し、ブラウザーが資格情報を公開せずに直接ストレージにファイルをアップロードできるようにします。

Azure Developer CLI のデプロイ

azd upを使用して完全なインフラストラクチャをデプロイします。 これにより、React フロントエンドと Fastify API バックエンド用の Azure Container Apps がプロビジョニングされ、マネージド ID が構成され、RBAC アクセス許可が割り当てられます。 インフラストラクチャでは、必要に応じて、Azure 検証済みモジュールで Azure Well-Architected Framework の原則に従って Bicep テンプレートを使用します。

開発コンテナー環境

このチュートリアルの 完全なサンプル コード では、 GitHub Codespaces またはローカル Visual Studio Code のいずれかの開発コンテナーを使用します。

このチュートリアルは、 Dev Containers 拡張機能を使用して Visual Studio Code でローカルで実行することもできます。 完全なサンプル コードには、開発コンテナーの構成が含まれています。

GitHub Codespaces でサンプルを開く

GitHub Codespaces は、すべての依存関係がプレインストールされたブラウザー ベースの VS Code 環境を提供します。

重要

すべての GitHub アカウントでは、毎月無料時間の Codespaces を使用できます。 詳細については、GitHub Codespaces の毎月含まれるストレージとコア時間を参照してください。

  1. Web ブラウザーで サンプル リポジトリ を開き、メインの Code>Create codespace を選択します。

    [ファイルに移動]、[ファイルの追加]、[緑色のコード] ボタンが強調表示されている GitHub リポジトリ ページのスクリーンショット。

  2. 開発コンテナーが起動するまで待ちます。 このスタートアップ プロセスには数分かかる場合があります。 このチュートリアルの残りの手順は、この開発コンテナーのコンテキストで行われます。

サンプルのデプロイ

  1. Azure にサインインします。

    azd auth login
    
  2. リソースをプロビジョニングし、ホスティング環境にサンプルをデプロイします。

    azd up
    

    メッセージが表示されたら、次の情報を入力します。

    プロンプト 入力する
    一意の環境名を入力する secure-upload
    使用する Azure サブスクリプションを選択する 一覧からサブスクリプションを選択する
    "location" インフラストラクチャ パラメーターの値を入力します 使用可能な場所から選択する

    または、プロビジョニングされたリソースを表示し、デプロイの出力を表示する場合は、次のコマンドを実行して、プロンプトなしでデプロイできます。

    azd provision
    

    次に、次のコマンドを実行して、アプリケーション コードをデプロイします。

    azd deploy
    

    API または Web アプリ コードを変更する場合は、次のいずれかのコマンドを使用して、アプリケーション コードのみを再デプロイできます。

    azd deploy app
    azd deploy api
    
  3. デプロイが完了したら、ターミナルに表示されるデプロイされた Web アプリの URL を書き留めます。

      (✓) Done: Deploying service app
      - Endpoint: https://app-gp2pofajnjhy6.calmtree-87e53015.eastus2.azurecontainerapps.io/
    

    これは URL の例です。 URL が異なります。

サンプルを試す

  1. デプロイされた Web アプリを新しいブラウザー タブで開き、アップロードする PNG ファイルを選択します。 ./docs/media フォルダーには複数の PNG ファイルがあります。

    ファイルを Azure Storage にアップロードするための Web アプリのスクリーンショット。[ファイルの選択] ボタンとコンテナー名のアップロードが表示されています。

  2. [ SAS トークンの取得] を選択し、[ ファイルのアップロード] を選択します。

  3. アップロードしたファイルを、アップロード ボタンの下にあるギャラリーで表示します。

    Azure Storage に daisies.jpg をアップロードした後の Web アプリのスクリーンショット。ファイル名、SAS URL、アップロードの状態、画像のサムネイルが表示されています。

何が起きたの。

  • 時間制限付きの書き込み専用 SAS トークンを使用して、ブラウザーから Azure Storage に直接アップロードされたファイル
  • ギャラリー イメージは、読み取り専用 SAS トークンを使用して Azure Storage から直接読み込まれます
  • ブラウザーで認証シークレットが公開されませんでした

コードのしくみ

アプリケーションの動作を確認したので、コードでセキュリティで保護されたファイルのアップロードを実装する方法を確認します。 アプリケーションには、次の 2 つの主要な部分があります。

  1. API バックエンド - Azure での認証と SAS トークンの生成
  2. React フロントエンド - SAS トークンを使用して Azure Storage に直接ファイルをアップロードする

次のセクションでは、主要なコード実装について説明します。

SAS トークンを生成してファイルを一覧表示する API サーバー

API サーバーは Azure Storage に対して認証を行い、ブラウザーで使用する時間制限付き SAS トークンを生成します。

マネージド ID を使用した認証

アプリケーションでは、認証にマネージド ID を持つユーザー委任キーが使用されます。これは、Azure アプリケーションにとって最も安全な方法です。 ChainedTokenCredentialは、次の順序で認証方法を試行します。

  1. Azure: ManagedIdentityCredential (Container Apps アイデンティティ)
  2. ローカル開発: AzureCliCredential ( az login セッション)
// From: packages/api/src/lib/azure-storage.ts
export function getCredential(): ChainedTokenCredential {
  if (!_credential) {
    const clientId = process.env.AZURE_CLIENT_ID;
    
    // Create credential chain with ManagedIdentity first
    const credentials = [
      new ManagedIdentityCredential(clientId ? { clientId } : undefined),
      new AzureCliCredential()
    ];
    
    _credential = new ChainedTokenCredential(...credentials);
  }
  return _credential;
}

認証後、Azure Storage と対話する BlobServiceClient を作成します。

// From: packages/api/src/lib/azure-storage.ts
export function getBlobServiceClient(accountName: string): BlobServiceClient {
  const credential = getCredential();
  const url = `https://${accountName}.blob.core.windows.net`;
  
  return new BlobServiceClient(url, credential);
}

ユーザー委任キーを使用して SAS トークンを生成する

SAS トークンには、ストレージ アカウント キーではなく Microsoft Entra ID 資格情報を使用してトークンを認証するユーザー委任キーが必要です。 キーは、特定の時間範囲に対して有効です。

const startsOn = new Date();
const expiresOn = new Date(startsOn.valueOf() + minutes * 60 * 1000);

const userDelegationKey = await blobServiceClient.getUserDelegationKey(
  startsOn,
  expiresOn
);

ファイルアップロード用の書き込み専用 SAS トークンを生成する

ファイルアップロードの場合、API はデータの読み取りまたは削除ができない書き込み専用トークンを生成します。 トークンは 10 分後に期限切れになります。

// From: packages/api/src/routes/sas.ts
const DEFAULT_SAS_TOKEN_PERMISSION = 'w';
const DEFAULT_SAS_TOKEN_EXPIRATION_MINUTES = 10;

const sasToken = generateBlobSASQueryParameters(
  {
    containerName: container,
    blobName: file,
    permissions: BlobSASPermissions.parse(permission),
    startsOn,
    expiresOn
  },
  userDelegationKey,
  accountName
).toString();

const sasUrl = `${blobClient.url}?${sasToken}`;

使用可能なアクセス許可レベル:

  • 'r' - 読み取り (ダウンロード/表示)
  • 'w' - 書き込み (アップロード/上書き) - アップロードに使用
  • 'd' -削除
  • 'c' -作成
  • 'a' - 追加 (BLOB の追加)

ファイルを一覧表示および表示するための読み取り専用 SAS トークンを生成する

ファイルを一覧表示して表示するために、API は 60 分後に有効期限が切れる読み取り専用トークンを生成します。

// From: packages/api/src/routes/list.ts
const LIST_SAS_TOKEN_PERMISSION = 'r';
const LIST_SAS_TOKEN_EXPIRATION_MINUTES = 60;

const sasToken = generateBlobSASQueryParameters(
  {
    containerName: container,
    blobName: blob.name,
    permissions: BlobSASPermissions.parse(LIST_SAS_TOKEN_PERMISSION),
    startsOn,
    expiresOn
  },
  userDelegationKey,
  accountName
).toString();

const sasUrl = `${blobClient.url}?${sasToken}`;

WEB アプリ クライアントが API サーバーから SAS トークンを要求および受信する

React フロントエンドは API から SAS トークンを要求し、それらを使用してブラウザーから Azure Storage にファイルを直接アップロードします。

フロントエンドは、次の 3 段階のプロセスに従います。

  1. 特定のファイルに対して API から SAS トークンを要求する
  2. SAS トークン URL を使用して Azure Storage に直接アップロードする
  3. 読み取り専用 SAS トークンを使用してアップロードされたファイルの一覧をフェッチして表示する

このアーキテクチャはバックエンドを軽量に保ちます。トークンのみが生成され、ファイル データは処理されません。

API サーバーから Blob Storage SAS トークンを要求する

ユーザーがファイルを選択し、[GET SAS Token]\(SAS トークンの取得\) をクリックすると、フロントエンドは API に書き込み専用 SAS トークンを要求します。

// From: packages/app/src/App.tsx
const handleFileSasToken = () => {
  const permission = 'w'; // write-only
  const timerange = 10;   // 10 minutes expiration

  if (!selectedFile) return;

  // Build API request URL
  const url = `${API_URL}/api/sas?file=${encodeURIComponent(
    selectedFile.name
  )}&permission=${permission}&container=${containerName}&timerange=${timerange}`;

  fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json'
    }
  })
    .then((response) => {
      if (!response.ok) {
        throw new Error(`Error: ${response.status} ${response.statusText}`);
      }
      return response.json();
    })
    .then((data: SasResponse) => {
      const { url } = data;
      setSasTokenUrl(url); // Store the SAS URL for upload
    });
};

何が起こるか:

  • フロントエンド送信: GET /api/sas?file=photo.jpg&permission=w&container=upload&timerange=10
  • API は次を返します。 { url: "https://storageaccount.blob.core.windows.net/upload/photo.jpg?sv=2024-05-04&..." }
  • この URL は 10 分間有効であり、その特定の BLOB への 書き込み専用 アクセスを許可します

SAS トークンを使用して Blob Storage に直接アップロードする

SAS トークン URL を受信すると、フロントエンドはファイルを ArrayBuffer に変換し、ファイルを 直接 Azure Storage に アップロードします。API 全体をバイパスします。 これにより、サーバーの負荷が軽減され、パフォーマンスが向上します。

ファイルを ArrayBuffer に変換します。

// From: packages/app/src/lib/convert-file-to-arraybuffer.ts
export function convertFileToArrayBuffer(file: File): Promise<ArrayBuffer | null> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = () => {
      const arrayBuffer = reader.result;
      resolve(arrayBuffer as ArrayBuffer);
    };

    reader.onerror = () => {
      reject(new Error('Error reading file.'));
    };

    reader.readAsArrayBuffer(file);
  });
}

次に、BlockBlobClient@azure/storage-blobを使用して、SAS トークン URL を使用してファイル データをアップロードします。

// From: packages/app/src/App.tsx
const handleFileUpload = () => {
  console.log('SAS Token URL:', sasTokenUrl);

  // Convert file to ArrayBuffer
  convertFileToArrayBuffer(selectedFile as File)
    .then((fileArrayBuffer) => {
      if (fileArrayBuffer === null || fileArrayBuffer.byteLength < 1) {
        throw new Error('Failed to convert file to ArrayBuffer');
      }

      // Create Azure Storage client with SAS URL
      const blockBlobClient = new BlockBlobClient(sasTokenUrl);
      
      // Upload directly to Azure Storage
      return blockBlobClient.uploadData(fileArrayBuffer);
    })
    .then((uploadResponse) => {
      if (!uploadResponse) {
        throw new Error('Upload failed - no response from Azure Storage');
      }
      setUploadStatus('Successfully finished upload');
      
      // After upload, fetch the updated list of files
      const listUrl = `${API_URL}/api/list?container=${containerName}`;
      return fetch(listUrl);
    });
};

重要なポイント:

  • ファイルが API サーバーを通過することはありません
  • アップロードがブラウザーから Azure Storage に直接送信される
  • SAS トークンが要求を認証する
  • ファイル処理のためのサーバー帯域幅または処理コストなし

Azure Storage から直接ファイルをフェッチし、サムネイル画像を表示する

アップロードが成功すると、フロントエンドはコンテナー内のすべてのファイルの一覧をフェッチします。 リスト内の各ファイルには、独自の 読み取り専用 SAS トークンが付属しています。

// From: packages/app/src/App.tsx
const listUrl = `${API_URL}/api/list?container=${containerName}`;

fetch(listUrl)
  .then((response) => {
    if (!response.ok) {
      throw new Error(`Error: ${response.status}`);
    }
    return response.json();
  })
  .then((data: ListResponse) => {
    setList(data.list); // Array of SAS URLs with read permission
  });

応答の例:

{
  "list": [
    "https://storageaccount.blob.core.windows.net/upload/photo1.jpg?sv=2024-05-04&se=2025-12-18T15:30:00Z&sr=b&sp=r&...",
    "https://storageaccount.blob.core.windows.net/upload/photo2.jpg?sv=2024-05-04&se=2025-12-18T15:30:00Z&sr=b&sp=r&..."
  ]
}

フロントエンドは、イメージ タグで直接 SAS URL を使用します。 ブラウザーは、埋め込み読み取り専用トークンを使用して Azure Storage からイメージをフェッチします。

// From: packages/app/src/App.tsx
<Grid container spacing={2}>
  {list.map((item) => {
    const urlWithoutQuery = item.split('?')[0];
    const filename = urlWithoutQuery.split('/').pop() || '';
    const isImage = filename.endsWith('.jpg') || 
                    filename.endsWith('.png') || 
                    filename.endsWith('.jpeg');
    
    return (
      <Grid item xs={6} sm={4} md={3} key={item}>
        <Card>
          {isImage ? (
            <CardMedia component="img" image={item} alt={filename} />
          ) : (
            <Typography>{filename}</Typography>
          )}
        </Card>
      </Grid>
    );
  })}
</Grid>

しくみ:

  • 一覧の各 URL には、読み取り専用 SAS トークンが含まれています (sp=r)
  • ブラウザーが Azure Storage に直接 GET 要求を行う
  • 認証は必要ありません - トークンは URL に含まれています
  • トークンは 60 分後に期限切れになります (API で構成済み)

リソースのクリーンアップ

このチュートリアルが完了したら、継続的な料金を回避するために、すべての Azure リソースを削除します。

azd down

トラブルシューティング

このサンプルに関する 問題を GitHub リポジトリで報告します。 問題に次の情報を含めてください。

  • 記事の URL
  • 問題が発生した記事内の手順またはコンテキスト
  • 開発環境

サンプル コード

次のステップ

Azure Storage にファイルを安全にアップロードする方法を学習したので、次の関連トピックを確認します。