マネージド ID がサポートされている Azure リソースには、Microsoft Entra 認証をサポートしている Azure リソースに接続するためにマネージド ID を指定するオプションが常に用意されています。 マネージド ID のサポートにより、開発者はコードで資格情報を管理しなくて済みます。 マネージド ID は、それをサポートしている Azure リソースを操作する場合に推奨される認証オプションです。 マネージド ID の概要をご覧ください。
このページでは、Azure Key Vault、Azure Storage、Microsoft SQL Server に接続できるように App Service を構成する方法について説明します。 マネージド ID をサポートしていて、Microsoft Entra 認証をサポートしているリソースに接続する任意の Azure リソースに対して同じ原則を使用できます。
コード サンプルでは、Azure Identity クライアント ライブラリを使用します。これは、接続で使用されるアクセス トークンの取得など、多くの手順が自動的に処理されるため、推奨される方法です。
マネージド ID ではどのようなリソースに接続できますか?
マネージド ID は、Microsoft Entra 認証をサポートする任意のリソースに接続できます。 一般に、マネージド ID で接続できるようにするためにリソースで必要な特別なサポートはありません。
一部のリソースでは、Microsoft Entra 認証がサポートされておらず、またクライアント ライブラリでトークンによる認証もサポートされていない場合があります。 マネージド ID を使用して、コードやアプリケーション構成に保存することなく資格情報に安全にアクセスする方法のガイダンスを続けてお読みください。
マネージド ID を作成する
マネージド ID には、システム割り当てとユーザー割り当ての 2 種類があります。 システム割り当て ID は、1 つの Azure リソースに直接リンクされます。 Azure リソースが削除されると、ID も削除されます。 ユーザー割り当てマネージド ID は複数の Azure リソースに関連付けることができ、そのライフサイクルはそれらのリソースから独立しています。
ほとんどのシナリオでは、ユーザー割り当てマネージド ID を使用することをお勧めします。 使用しているソース リソースがユーザー割り当てマネージド ID をサポートしていない場合は、そのリソース プロバイダーのドキュメントを参照して、システム割り当てマネージド ID を持つように構成する方法を確認する必要があります。
重要
マネージド ID の作成に使用されるアカウントには、新しいユーザー割り当てマネージド ID を作成するための "マネージド ID 共同作成者" などのロールが必要です。
任意のオプションを使用して、ユーザー割り当てマネージド ID を作成します。
ユーザー割り当てマネージド ID を作成したら、マネージド ID の作成時に返される clientId
と principalId
の値を書き留めます。 principalId
はアクセス許可の追加時に使用し、clientId
はアプリケーションのコードで使用します。
コードでマネージド ID を使用する前に、それを使用する App Service に割り当てる必要があります。 ユーザー割り当てマネージド ID を使用するように App Service を構成するプロセスでは、アプリ構成でマネージド ID のリソース識別子を指定する必要があります。
ID にアクセス許可を追加する
ユーザー割り当てマネージド ID を使用するように App Service を構成したら、その ID に必要なアクセス許可を付与します。 このシナリオでは、この ID を使用して Azure Storage を操作するため、 Azure ロール ベースのアクセス制御 (RBAC) システムを使用して ユーザー割り当てマネージド ID にリソースへのアクセス許可を付与する必要があります。
重要
ロールの割り当てを追加するには、ターゲット リソースの "ユーザー アクセス管理者" や "所有者" などのロールが必要です。 必ず、アプリケーションの実行に必要な最小限の特権を付与してください。
アクセスするリソースには、ID アクセス許可を付与する必要があります。 たとえば、キー コンテナーにアクセスするためにトークンを要求する場合、アプリや関数のマネージド ID を含むアクセス ポリシーも追加する必要があります。 それ以外の場合は、有効なトークンを使用する場合でも、Key Vault への呼び出しは拒否されます。 同じことが Azure SQL Database にも当てはまります。 どのリソースで Microsoft Entra トークンがサポートされるかについて詳しくは、「Microsoft Entra 認証をサポートする Azure サービス」を参照siteください。
コードでマネージド ID を使用する
上記の手順を完了すると、App Service には Azure リソースへのアクセス許可を持つマネージド ID があります。 マネージド ID を使用すると、コードに資格情報を格納する代わりに、コードが Azure リソースと対話するために使用できるアクセス トークンを取得できます。
お好みのプログラミング言語用の Azure ID ライブラリを使用することをお勧めします。 このライブラリはアクセス トークンを取得するため、ターゲット リソースへの接続が簡単になります。
Azure ID ライブラリの詳細については、以下をお読みください。
開発環境で Azure ID ライブラリを使用する
Azure ID ライブラリはそれぞれ、 DefaultAzureCredential
の種類を提供します。 DefaultAzureCredential
では、環境変数や対話型のサインインなどの複数のメカニズムを介した認証が自動的に試行されます。 この資格情報の種類は、独自の資格情報を使用する開発環境で使用できます。 また、マネージド ID を使用する Azure 運用環境でも使用できます。 アプリケーションをデプロイするときにコードを変更する必要はありません。
ユーザー割り当てマネージド ID を使用している場合は、ID のクライアント ID をパラメーターとして渡すことによって、認証するユーザー割り当てマネージド ID を明示的に指定する必要もあります。 Azure portal で ID を参照することで、クライアント ID を取得できます。
Azure Storage で BLOB にアクセスする
using Azure.Identity;
using Azure.Storage.Blobs;
// code omitted for brevity
// Specify the Client ID if using user-assigned managed identities
var clientID = Environment.GetEnvironmentVariable("Managed_Identity_Client_ID");
var credentialOptions = new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = clientID
};
var credential = new DefaultAzureCredential(credentialOptions);
var blobServiceClient1 = new BlobServiceClient(new Uri("<URI of Storage account>"), credential);
BlobContainerClient containerClient1 = blobServiceClient1.GetBlobContainerClient("<name of blob>");
BlobClient blobClient1 = containerClient1.GetBlobClient("<name of file>");
if (blobClient1.Exists())
{
var downloadedBlob = blobClient1.Download();
string blobContents = downloadedBlob.Value.Content.ToString();
}
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
// read the Client ID from your environment variables
String clientID = System.getProperty("Client_ID");
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
.managedIdentityClientId(clientID)
.build();
BlobServiceClient blobStorageClient = new BlobServiceClientBuilder()
.endpoint("<URI of Storage account>")
.credential(credential)
.buildClient();
BlobContainerClient blobContainerClient = blobStorageClient.getBlobContainerClient("<name of blob container>");
BlobClient blobClient = blobContainerClient.getBlobClient("<name of blob/file>");
if (blobClient.exists()) {
String blobContent = blobClient.downloadContent().toString();
}
import { DefaultAzureCredential } from "@azure/identity";
import { BlobServiceClient } from "@azure/storage-blob";
// Specify the Client ID if using user-assigned managed identities
const clientID = process.env.Managed_Identity_Client_ID;
const credential = new DefaultAzureCredential({
managedIdentityClientId: clientID
});
const blobServiceClient = new BlobServiceClient("<URI of Storage account>", credential);
const containerClient = blobServiceClient.getContainerClient("<name of blob>");
const blobClient = containerClient.getBlobClient("<name of file>");
async function downloadBlob() {
if (await blobClient.exists()) {
const downloadBlockBlobResponse = await blobClient.download();
const downloadedBlob = await streamToString(downloadBlockBlobResponse.readableStreamBody);
console.log("Downloaded blob content:", downloadedBlob);
}
}
async function streamToString(readableStream) {
return new Promise((resolve, reject) => {
const chunks = [];
readableStream.on("data", (data) => {
chunks.push(data.toString());
});
readableStream.on("end", () => {
resolve(chunks.join(""));
});
readableStream.on("error", reject);
});
}
downloadBlob().catch(console.error);
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
import os
# Specify the Client ID if using user-assigned managed identities
client_id = os.getenv("Managed_Identity_Client_ID")
credential = DefaultAzureCredential(managed_identity_client_id=client_id)
blob_service_client = BlobServiceClient(account_url="<URI of Storage account>", credential=credential)
container_client = blob_service_client.get_container_client("<name of blob>")
blob_client = container_client.get_blob_client("<name of file>")
def download_blob():
if blob_client.exists():
download_stream = blob_client.download_blob()
blob_contents = download_stream.readall().decode('utf-8')
print("Downloaded blob content:", blob_contents)
download_blob()
package main
import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
)
func main() {
// The client ID for the user-assigned managed identity is read from the AZURE_CLIENT_ID env var
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
fmt.Printf("failed to obtain a credential: %v\n", err)
return
}
accountURL := "<URI of Storage account>"
containerName := "<name of blob>"
blobName := "<name of file>"
serviceClient, err := azblob.NewServiceClient(accountURL, cred, nil)
if err != nil {
fmt.Printf("failed to create service client: %v\n", err)
return
}
containerClient := serviceClient.NewContainerClient(containerName)
blobClient := containerClient.NewBlobClient(blobName)
// Check if the blob exists
_, err = blobClient.GetProperties(context.Background(), nil)
if err != nil {
fmt.Printf("failed to get blob properties: %v\n", err)
return
}
// Download the blob
downloadResponse, err := blobClient.Download(context.Background(), nil)
if err != nil {
fmt.Printf("failed to download blob: %v\n", err)
return
}
// Read the blob content
blobData := downloadResponse.Body(nil)
defer blobData.Close()
blobContents := new(strings.Builder)
_, err = io.Copy(blobContents, blobData)
if err != nil {
fmt.Printf("failed to read blob data: %v\n", err)
return
}
fmt.Println("Downloaded blob content:", blobContents.String())
}
Azure Key Vault に格納されているシークレットにアクセスする
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Azure.Core;
// code omitted for brevity
// Specify the Client ID if using user-assigned managed identities
var clientID = Environment.GetEnvironmentVariable("Managed_Identity_Client_ID");
var credentialOptions = new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = clientID
};
var credential = new DefaultAzureCredential(credentialOptions);
var client = new SecretClient(
new Uri("https://<your-unique-key-vault-name>.vault.azure.net/"),
credential);
KeyVaultSecret secret = client.GetSecret("<my secret>");
string secretValue = secret.Value;
import com.azure.core.util.polling.SyncPoller;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;
import com.azure.security.keyvault.secrets.models.DeletedSecret;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
String keyVaultName = "mykeyvault";
String keyVaultUri = "https://" + keyVaultName + ".vault.azure.net";
String secretName = "mysecret";
// read the user-assigned managed identity Client ID from your environment variables
String clientID = System.getProperty("Managed_Identity_Client_ID");
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
.managedIdentityClientId(clientID)
.build();
SecretClient secretClient = new SecretClientBuilder()
.vaultUrl(keyVaultUri)
.credential(credential)
.buildClient();
KeyVaultSecret retrievedSecret = secretClient.getSecret(secretName);
import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
// Specify the Client ID if using user-assigned managed identities
const clientID = process.env.Managed_Identity_Client_ID;
const credential = new DefaultAzureCredential({
managedIdentityClientId: clientID
});
const client = new SecretClient("https://<your-key-vault-name>.vault.azure.net/", credential);
async function getSecret() {
const secret = await client.getSecret("<your-secret-name>");
const secretValue = secret.value;
console.log(secretValue);
}
getSecret().catch(err => console.error("Error retrieving secret:", err));
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
import os
# Specify the Client ID if using user-assigned managed identities
client_id = os.getenv("Managed_Identity_Client_ID")
credential = DefaultAzureCredential(managed_identity_client_id=client_id)
client = SecretClient(vault_url="https://<your-key-vault-name>.vault.azure.net/", credential=credential)
def get_secret():
secret = client.get_secret("<your-secret-name>")
secret_value = secret.value
print(secret_value)
if __name__ == "__main__":
try:
get_secret()
except Exception as e:
print(f"Error retrieving secret: {e}")
package main
import (
"context"
"fmt"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)
func main() {
// The client ID for the user-assigned managed identity is read from the AZURE_CLIENT_ID env var
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
fmt.Printf("failed to obtain a credential: %v\n", err)
return
}
client, err := azsecrets.NewClient("https://<your-key-vault-name>.vault.azure.net/", credential, nil)
if err != nil {
fmt.Printf("Failed to create secret client: %v\n", err)
return
}
secretResp, err := client.GetSecret(context.TODO(), "<your-secret-name>", nil)
if err != nil {
fmt.Printf("Failed to get secret: %v\n", err)
return
}
secretValue := *secretResp.Value
fmt.Println(secretValue)
}
Azure SQL データベースにアクセスする
using Azure.Identity;
using Microsoft.Data.SqlClient;
// code omitted for brevity
// Specify the Client ID if using user-assigned managed identities
var clientID = Environment.GetEnvironmentVariable("Managed_Identity_Client_ID");
var credentialOptions = new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = clientID
};
AccessToken accessToken = await new DefaultAzureCredential(credentialOptions).GetTokenAsync(
new TokenRequestContext(new string[] { "https://database.windows.net//.default" }));
using var connection = new SqlConnection("Server=<DB Server>; Database=<DB Name>;")
{
AccessToken = accessToken.Token
};
var cmd = new SqlCommand("select top 1 ColumnName from TableName", connection);
await connection.OpenAsync();
SqlDataReader dr = cmd.ExecuteReader();
while(dr.Read())
{
Console.WriteLine(dr.GetValue(0).ToString());
}
dr.Close();
Azure Spring Apps を使用すると、コードに変更を加えることなく、マネージド ID を使用して Azure SQL データベースに接続できます。
src/main/resources/application.properties
ファイルを開き、次の行の末尾に Authentication=ActiveDirectoryMSI;
を追加します。 $AZ_DATABASE_NAME
変数には必ず正しい値を使用してください。
spring.datasource.url=jdbc:sqlserver://$AZ_DATABASE_NAME.database.windows.net:1433;database=demo;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;Authentication=ActiveDirectoryMSI;
マネージド ID を使用して Azure SQL Database を Azure Spring Apps アプリに接続する方法の詳細をご覧ください。
import { DefaultAzureCredential } from "@azure/identity";
import { Connection, Request } from "tedious";
// Specify the Client ID if using a user-assigned managed identity
const clientID = process.env.Managed_Identity_Client_ID;
const credential = new DefaultAzureCredential({
managedIdentityClientId: clientID
});
async function getAccessToken() {
const tokenResponse = await credential.getToken("https://database.windows.net//.default");
return tokenResponse.token;
}
async function queryDatabase() {
const accessToken = await getAccessToken();
const config = {
server: "<your-server-name>",
authentication: {
type: "azure-active-directory-access-token",
options: {
token: accessToken
}
},
options: {
database: "<your-database-name>",
encrypt: true
}
};
const connection = new Connection(config);
connection.on("connect", err => {
if (err) {
console.error("Connection failed:", err);
return;
}
const request = new Request("SELECT TOP 1 ColumnName FROM TableName", (err, rowCount, rows) => {
if (err) {
console.error("Query failed:", err);
return;
}
rows.forEach(row => {
console.log(row.value);
});
connection.close();
});
connection.execSql(request);
});
connection.connect();
}
queryDatabase().catch(err => console.error("Error:", err));
import os
from azure.identity import DefaultAzureCredential
from azure.core.credentials import AccessToken
import pyodbc
# Specify the Client ID if using a user-assigned managed identity
client_id = os.getenv("Managed_Identity_Client_ID")
credential = DefaultAzureCredential(managed_identity_client_id=client_id)
# Get the access token
token = credential.get_token("https://database.windows.net//.default")
access_token = token.token
# Set up the connection string
connection_string = "Driver={ODBC Driver 18 for SQL Server};Server=<your-server-name>;Database=<your-database-name>;"
# Connect to the database
connection = pyodbc.connect(connection_string, attrs_before={"AccessToken": access_token})
# Execute the query
cursor = connection.cursor()
cursor.execute("SELECT TOP 1 ColumnName FROM TableName")
# Fetch and print the result
row = cursor.fetchone()
while row:
print(row)
row = cursor.fetchone()
# Close the connection
cursor.close()
connection.close()
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/denisenkom/go-mssqldb"
)
func main() {
// The client ID for the user-assigned managed identity is read from the AZURE_CLIENT_ID env var
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
fmt.Printf("failed to obtain a credential: %v\n", err)
return
}
// Get the access token
token, err := credential.GetToken(context.TODO(), azidentity.TokenRequestOptions{
Scopes: []string{"https://database.windows.net//.default"},
})
if err != nil {
fmt.Printf("Failed to get token: %v\n", err)
return
}
// Set up the connection string
connString := fmt.Sprintf("sqlserver://<your-server-name>?database=<your-database-name>&access_token=%s", token.Token)
// Connect to the database
db, err := sql.Open("sqlserver", connString)
if err != nil {
fmt.Printf("Failed to connect to the database: %v\n", err)
return
}
defer db.Close()
// Execute the query
rows, err := db.QueryContext(context.TODO(), "SELECT TOP 1 ColumnName FROM TableName")
if err != nil {
fmt.Printf("Failed to execute query: %v\n", err)
return
}
defer rows.Close()
// Fetch and print the result
for rows.Next() {
var columnValue string
if err := rows.Scan(&columnValue); err != nil {
fmt.Printf("Failed to scan row: %v\n", err)
return
}
fmt.Println(columnValue)
}
}
Microsoft Entra ID またはライブラリのトークン ベースの認証をサポートしていないリソースに接続する
一部の Azure リソースでは、まだ Microsoft Entra 認証がサポートされていない場合や、そのクライアント ライブラリでトークンによる認証をサポートしていない場合があります。 通常、こうしたリソースは、ユーザー名とパスワードまたは接続文字列内のアクセス キーを必要とするオープンソース テクノロジです。
コードやアプリケーション構成に資格情報を格納しないようにするには、資格情報をシークレットとして Azure Key Vault に格納します。 上記の例を使用すると、マネージド ID を使用して Azure KeyVault からシークレットを取得し、接続文字列に資格情報を渡すことができます。 この方法は、コードや環境で資格情報を直接処理する必要がないことを意味します。
トークンを直接処理する場合のガイドライン
一部のシナリオでは、組み込みのメソッドを使用してターゲット リソースに接続するのではなく、マネージド ID のトークンを手動で取得する必要がある場合があります。 このようなシナリオとしては、使用しているプログラミング言語や接続先のターゲット リソース用のクライアント ライブラリが存在しない場合や、Azure で実行されていないリソースに接続する場合などが挙げられます。 トークンを手動で取得する場合は、以下のガイドラインに従ってください。
取得したトークンをキャッシュする
パフォーマンスと信頼性を確保するために、アプリケーションでトークンをローカル メモリにキャッシュするか、ディスクに保存する場合は暗号化することをお勧めします。 マネージド ID トークンは 24 時間有効であり、新しいトークンを定期的に要求しても、キャッシュされたものがトークン発行エンドポイントから返されるため、メリットはありません。 要求の制限を超えると、レート制限が適用され、HTTP 429 エラーが出されます。
トークンを取得するときに、トークンの生成時に返される expires_on
(または同等のプロパティ) が示す期限の 5 分前にトークン キャッシュの有効期限が切れるように設定できます。
トークン検査
アプリケーションはトークンのコンテンツに依存しないようにする必要があります。 トークンのコンテンツは、トークンを要求しているクライアントではなく、アクセス対象 (ターゲット リソース) のみが使用するためのものです。 トークンのコンテンツは、将来変更されたり、暗号化されたりする可能性があります。
トークンを公開または移動しない
トークンは資格情報と同様に扱う必要があります。 ユーザーやその他のサービス (ログ/監視ソリューションなど) に公開しないでください。 ターゲット リソースに対して認証するとき以外は、それが使用されているソース リソースから移動しないでください。
次の手順