none
Azure REST APIをプログラム上から呼び出すと401が返ってくる RRS feed

  • 質問

  • お世話になります。

    .netcoreでUsage Details - Listをコールして課金額を取得したいと思っています。

    Azure REST API ReferenceからTryItでログインし、APIをコールするとweb上では成功するのですが
    プログラム上からはThe remote server returned an error: (401) Unauthorized.が返ってきます。

    ただし、Usage Details - Listから取得したトークンをコピーしてプログラム上からアクセスすると200が返ってきます。

    using Microsoft.Extensions.Configuration;
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Text;
    using static AzureCostConsoleAppTest.UsageDetails;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    public static async Task<string> GetAccessToken()
    {
    
        var requestUri = new Uri($"https://login.microsoftonline.com/" + config["AppSettings:TenantId"] + "/oauth2/v2.0/token");
        var requestContent = new FormUrlEncodedContent(
            new Dictionary<string, string>
            {
                {"client_id", config["AppSettings:ClientId"] },
                {"scope", "https://graph.microsoft.com/.default" },
                {"client_secret", config["AppSettings:ClientSecret"] },
                {"grant_type", "client_credentials" }
            });
    
        HttpClient Client = new HttpClient();
    
        using (var response = await Client.PostAsync(requestUri, requestContent).ConfigureAwait(false))
        {
            var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            var responseJson = JsonConvert.DeserializeObject<JsonForAccessToken>(responseString);
    
            return responseJson.AccessToken;
        }
    }

    上記プログラムからアクセストークンは取得できるので、Azure Active Directory→既定のディレクトリ - アプリの登録で作成したアプリケーションの API のアクセス許可の設定が正しくないのが原因だと思うのですが、所説辿ってもどのAPIを有効にすれば取得できるのか資料が見当たりません。

    プログラム上からWebAPIをコールするのに必要なアクセス許可の設定方法を記述した資料等は無いでしょうか。


    2019年8月23日 14:22

すべての返信

  • 401で認証エラーになっているのはOAuthアクセストークンのscopeが、tokenを渡す対象のドメインと異なってるので拒否されてるからじゃないかな。

    Usage Details - ListのTry Itとはdocs.microsoft.comのことでしょうか?
    手持ちのアカウントでこのAPIを試すと応答400(Billing Period is not supported in (2019-05-01) API Version for Subscription Scope With Web Direct Offer. )になるので正しく動くものかわかりません。

    別のAPIなら動くのでそっちで試してみる。

    namespace ConsoleApp1
    {
        using System;
        using System.Collections.Generic;
        using System.Net;
        using System.Net.Http;
        using System.Threading.Tasks;
    
        class Program
        {
            //プロジェクトのプロパティで、ビルドの詳細の言語バージョンを7.1以降に
            static async Task Main(string[] args)
            {
                string subscriptionId = ApplicationConstant.SUBSCRIPTION_ID;
                string resourceGroupName = ApplicationConstant.APPSERVICE_RESOURCE_GROUP_NAME;
                string appname = ApplicationConstant.APPSERVICE_NAME;
    
                //AppServiceの情報を取るREST APIで試してみる
                //https://docs.microsoft.com/ja-jp/rest/api/appservice/webapps/get
                string url;
                url = "https://management.azure.com/"
                    + $@"subscriptions/{subscriptionId}"
                    + $@"/resourceGroups/{resourceGroupName}"
                    + $@"/providers/Microsoft.Web/sites/{appname}"
                    + "?api-version=2016-08-01";
    
                var token = await new Access().GetAccessToken();
                if (!token.IsSuccess)
                {
                    WriteError(token.Message?.ToString());
                    return;
                }
    
                using (System.Net.Http.HttpClient client = new HttpClient())
                {
                    //ヘッダにトークンを追加
                    token.AddHeader(client);
    
                    var response = await client.GetAsync(url);
    
                    System.Console.WriteLine(response.StatusCode);
                    if (response.StatusCode != HttpStatusCode.OK)
                    {
                        //RESTが間違ってたり、拒否されたり
                        WriteError(await response.Content.ReadAsStringAsync());
                        return;
                    }
                    else
                    {
                        //成功
                        string text = await response.Content.ReadAsStringAsync();
                        Console.WriteLine(text);
                    }
                }
    
                Console.ReadLine();
            }
    
            private static void WriteError(string message)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Error.WriteLine(message);
                Console.ResetColor();
            }
    
        }
    
        class Access
        {
            public string ClientSecret { get; set; } = ApplicationConstant.CLIENT_SECRET;
            public string ClientId { get; set; } = ApplicationConstant.CLIENT_ID;
            public string TenantId { get; set; } = ApplicationConstant.TENANT_ID;
    
            /// <summary>スコープはアクセスしたいドメインに</summary>
            public string Scope = "https://management.azure.com/.default";
    
            public async Task<Token> GetAccessToken()
            {
                //アクセストークン取得のためのアドレス
                string url = $@"https://login.microsoftonline.com/{this.TenantId}/oauth2/v2.0/token";
    
                //clinet_id,client_secretはAzureポータルから取得しておく
                Dictionary<string, string> dic = new Dictionary<string, string>()
                {
                    { "client_id", this.ClientId },
                    { "client_secret", this.ClientSecret},
                    { "grant_type", "client_credentials" },
                    { "scope", this.Scope}
                };
    
                var postcontent = new FormUrlEncodedContent(dic);
    
                using (HttpClient client = new HttpClient())
                {
                    using (HttpResponseMessage response = await client.PostAsync(url, postcontent))
                    {
                        if (!response.IsSuccessStatusCode)
                        {
                            //トークン取得失敗
                            Token token = new Token();
                            token.Message = await response.Content.ReadAsStringAsync();
                            token.IsSuccess = false;
                            return token;
                        }
                        else
                        {
                            //トークン取得成功
                            string json = await response.Content.ReadAsStringAsync();
                            Token token = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(json);
                            token.IsSuccess = true;
                            return token;
                        }
                    }
                }
            }
    
    
        }
    
        class Token
        {
            public string Token_Type { get; set; }
            public string Access_Token { get; set; }
            public int Ext_Expires_In { get; set; }
            public int Expires_In { get; set; }
    
            [Newtonsoft.Json.JsonIgnore()]
            public bool IsSuccess { get; set; }
    
            [Newtonsoft.Json.JsonIgnore()]
            public object Message { get; set; }
    
            [Newtonsoft.Json.JsonIgnore()]
            public bool IsBearer { get { return this.Token_Type == "Bearer"; } }
            [Newtonsoft.Json.JsonIgnore()]
            public string BearerString { get { return Token_Type + " " + Access_Token; } }
    
            public override string ToString()
            {
                return BearerString;
            }
    
            public void AddHeader(System.Net.Http.HttpClient client)
            {
                client.DefaultRequestHeaders.Add("Authorization", BearerString);
            }
        }
    
        /*
        /// <summary>各種値</summary>
        static partial class ApplicationConstant
        {
            //https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps
            //でアプリを登録。
            //登録したアプリの「概要」からクライアントIDとテナントIDを取得
            //登録したアプリの「証明書とシークレット」でクライアントシークレットを追加して値を取得
            public static string CLIENT_SECRET = "";
            public static string CLIENT_ID = "";
            public static string TENANT_ID = "";
    
            //アプリを登録出来たらアプリがRESTでやりとりする情報へのアクセス権限を設定してやる必要がある。
            //アクセス権限がないとRESTの応答は403
            //Azureのサブスクリプションのページの「アクセス制御(IAM)」を開いて「ロールの割り当ての追加」で
            //  役割 : 共同作成者
            //  アクセスの割当先 : Azure ADのユーザー、グループ、サービス、プリンシパル
            //  選択 : ここに登録したアプリ名を入力するとアプリが検索されるので選択
    
            //REST APIの対象としたいAzureのサブスクリプションのページにあるサブスクリプションID
            public static string SUBSCRIPTION_ID="";
    
            public static string APPSERVICE_RESOURCE_GROUP_NAME = "";
            public static string APPSERVICE_NAME = "";//App Serviceのアプリ名
        }
        */
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2019年8月24日 5:18
  • s_tsurumakiさん、こんにちは。フォーラムオペレーターのHarukaです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    ご質問いただいた件ですが、その後いかがでしょうか。
    gekkaさんから寄せられた投稿はお役に立ちましたか。

    参考になった投稿には [回答としてマーク] をお願い致します。

    設定いただくことで、
    他のユーザーもお役に立つ回答を見つけやすくなります。

    お手数ですが、ご協力の程どうかよろしくお願いいたします。


    MSDN/ TechNet Community Support Haruka

    ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、
    ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    2019年8月28日 2:30
    モデレータ