--- lang: ja-jp breaks: true --- # 参考資料 > cURL で ASP.NET Core の Web API を呼び出す > https://learn.microsoft.com/ja-jp/azure/active-directory/develop/howto-call-a-web-api-with-curl?tabs=dotnet6&pivots=no-api > ms-identity-docs-code-dotnet/web-api/ > https://github.com/Azure-Samples/ms-identity-docs-code-dotnet/tree/main/web-api ![](https://hackmd.io/_uploads/SyGcSPocn.png) ## 前提条件 * アクティブなサブスクリプションを持つAzureアカウント。 * ※無料アカウントでも可能。 * ## Microsoft ID プラットフォームへのアプリケーションの登録 ### WebAPI の登録 ![](https://hackmd.io/_uploads/SyRkFMB93.png) * `Azure Active Directory` に WebAPI をアプリとして登録する。 * ![](https://hackmd.io/_uploads/HyWWKfr93.png) * ![](https://hackmd.io/_uploads/BJSmtfS53.png) * ![](https://hackmd.io/_uploads/S14qFMS93.png) * リダイレクト URIは空のままで良い。 * ![](https://hackmd.io/_uploads/r1aaYzS5n.png) * 「ディレクトリ (テナント) ID」「アプリケーション (クライアント) ID」 を控えておく。 * ![](https://hackmd.io/_uploads/BygQ9zH5n.png) * #### API を公開する * WebAPIに外部に公開するスコープを定義することで、アクセス権限を設定する。 * ※ クライアントアプリケーションがリクエストと同時に渡す `アクセス トークン` が有効な場合にのみ要求に応じるように構成する。 * ![](https://hackmd.io/_uploads/rym7hMH9n.png) * ![](https://hackmd.io/_uploads/B1zEhGS92.png) * ![](https://hackmd.io/_uploads/ry0UhfBqh.png) * 特に変更せずに「保存してから続ける」をクリックする。 * ![](https://hackmd.io/_uploads/HyTv2GH9h.png) * ![](https://hackmd.io/_uploads/SkjZaGBc3.png) * ![](https://hackmd.io/_uploads/B1_G6GBc3.png) * ![](https://hackmd.io/_uploads/rJpdTfS9h.png) * ![](https://hackmd.io/_uploads/B1qtTzH52.png) * ![](https://hackmd.io/_uploads/ByGhTGB52.png) * ![](https://hackmd.io/_uploads/Sk33pfS5n.png) * ![](https://hackmd.io/_uploads/BkLapMS9h.png) * ![](https://hackmd.io/_uploads/SJKWCzr93.png) ### アクセストークンを受け取る Webアプリ の登録 * `アクセストークン` を受け取る為のWebアプリの作成が別途必要。 * ![](https://hackmd.io/_uploads/SyRkFMB93.png) * `Azure Active Directory` に Webアプリ をアプリとして登録する。 * ![](https://hackmd.io/_uploads/HyWWKfr93.png) * ![](https://hackmd.io/_uploads/BJSmtfS53.png) * ![](https://hackmd.io/_uploads/H1CpyQrqn.png) * 作成した Webアプリ が受け取る `リダイレクトURI` を設定する。 * ![](https://hackmd.io/_uploads/HyNbg7rqn.png) * 「Web」を選択する。 * 開発及びテストの際は、`http://localhost` でも可能。 * ただし、`/authorize` エンドポイントにGETする際の、`redirect_uri`パラメータと必ず一致する必要がある。一致しない場合はエラーとなる。 * ![](https://hackmd.io/_uploads/Sy4MxQHq2.png) * #### クライアント シークレットの追加 * アプリが自分自身を識別する為に使用する `クライアントシークレット` (※`アプリケーションパスワード`)を構成します。 * Webアプリが `アクセストークン` を要求する際に `クライアントシークレット` が必要になる。 * クライアント シークレットを追加する * ![](https://hackmd.io/_uploads/r1TW-7Sqn.png) * ![](https://hackmd.io/_uploads/rkV7WXHq3.png) * ![](https://hackmd.io/_uploads/Skp4bXrq2.png) * ![](https://hackmd.io/_uploads/rJC8b7Sc3.png) * ![](https://hackmd.io/_uploads/rkrDZXHc2.png) * ![](https://hackmd.io/_uploads/rk35bmS53.png) * `クライアントシークレット`には 有効期限があり、二度と表示されない為登録時に控えておく必要がある。 #### アプリケーション権限を追加して WebAPI へのアクセスを許可する * `アクセストークン` を受け取る Webアプリ に WebAPI へのアクセススコープを指定することで、Microsoft ID platform が提供するスコープを含む`アクセストークン` を受け取ることが出来るようになる。 * ![](https://hackmd.io/_uploads/H1FMMmBqn.png) * ![](https://hackmd.io/_uploads/By9mfmS93.png) * ![](https://hackmd.io/_uploads/HyeOGXBc2.png) * ![](https://hackmd.io/_uploads/SJLLcQHcn.png) * ![](https://hackmd.io/_uploads/Hynu5mBq2.png) * 「委任されたアクセス許可」を選択する。 * ![](https://hackmd.io/_uploads/rkQgHsw9h.png) * アクセス許可にチェックを入れる * ![](https://hackmd.io/_uploads/ryS29mr53.png) * ![](https://hackmd.io/_uploads/rkBa9XBq2.png) * ![](https://hackmd.io/_uploads/r1kmsQr5h.png) * ![](https://hackmd.io/_uploads/B1wEimB53.png) * ![](https://hackmd.io/_uploads/S1ovUuD5n.png) * ![](https://hackmd.io/_uploads/rkY5sQrcn.png) * ## WebAPI をテストする ### 承認コードを要求する * クライアントアプリケーションがユーザを `/authorize` エンドポイントに誘導する。 * `/authorize` エンドポイントからのレスポンスはHTML及びJavaScriptとなる為、必ずWebブラウザを使用する必要がある。 * リクエストに必要な情報 * WebアプリのテナントID * WebアプリのクライアントID * WebAPIのクライアントID * ブラウザが アクセストークンを受け取る Webアプリ にリダイレクトされる。 * 取得される情報 * 認可コード:`?code={authorization_code}` ### 承認コード を使用して アクセス トークン を取得する * `/oauth2/v2.0/token` エンドポイントにアクセスし、`アクセストークン` を取得する。 * 必要なパラメータ * WebアプリのテナントID * WebアプリのクライアントID * WebAPIクライアントID * 認可コード * クライアントシークレット * ### アクセス トークンを使用して WebAPI を呼び出す * HTTPリクエストヘッダに `"Authorization: Bearer {access_token}"` を追加して WebAPIを呼び出す。 * ## アクセストークンを取得するWebアプリ ### テンプレートからプロジェクトを作成 ![](https://hackmd.io/_uploads/r1-FC889n.png) ![](https://hackmd.io/_uploads/S1H30LL9h.png) ### アクセストークンの取得を要求するページを追加する * ![](https://hackmd.io/_uploads/ByoAyPUch.png) * ![](https://hackmd.io/_uploads/SkTJgvUc2.png) * ![](https://hackmd.io/_uploads/HkfGlDU9h.png) * #### MsIdAccessTokenRequest.cshtml ```csharp= @page @model ToReceiveAccessToken.Pages.MsIdAccessTokenRequestModel @{ ViewData["Title"] = "Microsoft ID プラットフォーム アクセストークン取得要求"; } <h1>@ViewData["Title"]</h1> <p>このページは、Microsoft ID プラットフォーム のアクセストークンの取得要求を行います。</p> <form method="post"> <input type="submit" class="btn btn-primary" asp-page-handler="MsIdAccessTokenRequest" value="アクセストークン取得要求" /> </form> <p>@Model.Output</p> <p>@Model.RequestUrl</p> ``` #### MsIdAccessTokenRequest.cshtml.cs ```csharp= public class MsIdAccessTokenRequestModel : PageModel { public string Output { get; set; } public string RequestUrl { get; set; } public MsIdAccessTokenRequestModel() : base() { Output = ""; RequestUrl = ""; } public async Task<IActionResult> OnPostMsIdAccessTokenRequest() { string url = await RequestAuthorizationCode(base.Request); Output = $"アクセストークンの要求を行いました。"; RequestUrl = url; return Redirect(url); //return Page(); } private async Task<string> RequestAuthorizationCode( HttpRequest request ) { string tenant_id = "テナントID"; string web_app_calls_web_api_application_client_id = "WebアプリのクライアントID"; string redirect_uri = $"http{(request.IsHttps?"s":"")}://{request.Host}/MsIdRedirect"; string web_API_application_client_id = "WebAPIのクライアントID"; string scope = "API.GETPOST"; string url = $"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize?client_id={web_app_calls_web_api_application_client_id}&response_type=code&redirect_uri={redirect_uri}&response_mode=query&scope=api://{web_API_application_client_id}/{scope}"; return url; } } ``` ### リダイレクトを受け付けて、アクセストークンを取得するページを追加する * ![](https://hackmd.io/_uploads/SyIGWDL53.png) * ![](https://hackmd.io/_uploads/HJGm-w85n.png) * ![](https://hackmd.io/_uploads/SyxIbvU93.png) #### MsIdRedirect.cshtml ```csharp= @page @model ToReceiveAccessToken.Pages.MsIdRedirectModel @{ ViewData["Title"] = "Microsoft ID プラットフォーム のリダイレクトページ"; } <h1>@ViewData["Title"]</h1> <p>このページは、Microsoft ID プラットフォーム のリダイレクトページです。</p> @if (string.IsNullOrEmpty(Model.AuthorizationCode) == false) { <p>@Model.Output</p> <form method="post"> <input type="submit" class="btn btn-primary" asp-page-handler="MsIdAccessTokenRequest" value="アクセストークン取得" /> <input type="hidden" id="AuthorizationCode" name="AuthorizationCode" asp-for="AuthorizationCode" /> </form> } ``` #### MsIdRedirect.cshtml.cs ```cshap= public class MsIdRedirectModel : PageModel { public string Output { get; set; } = ""; [BindProperty] public string AuthorizationCode { get; set; } = ""; private static HttpClient? _httpClient; public Dictionary<string, object?>? ResponseJson = null; public async Task OnGet() { foreach (var getQuery in base.Request.Query) { Debug.WriteLine($"{getQuery.Key}:{getQuery.Value}"); } base.Request.Query.TryGetValue("code", out Microsoft.Extensions.Primitives.StringValues code); // `session_state` は必要ない。 base.Request.Query.TryGetValue("session_state", out Microsoft.Extensions.Primitives.StringValues session_state); AuthorizationCode = code.ToString(); Output = $"認証コード=[{code}]"; } public async Task<IActionResult> OnPostMsIdAccessTokenRequest() { Output = ""; if (string.IsNullOrEmpty(AuthorizationCode) == false) { try { Dictionary<string, object?>? responseJson = await RequestAuthorizationCode(base.Request, AuthorizationCode); Output = $"アクセストークンが取得されました。"; ResponseJson = responseJson; } catch (Exception ex) { Output = $"アクセストークンの取得に失敗しました。{ex.Message}"; } } return Page(); } private async Task<Dictionary<string, object?>?> RequestAuthorizationCode( HttpRequest request, string authorization_code ) { string tenant_id = "テナントID"; string web_app_calls_web_api_application_client_id = "WebアプリのクライアントID"; string redirect_uri = $"http{(request.IsHttps ? "s" : "")}://{request.Host}/MsIdRedirect"; string web_API_application_client_id = "WebAPIのクライアントID"; string scope = "API.GETPOST"; string client_secret = "Webアプリのクライアントシークレット"; string url = $"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"; string client_id = $"{web_app_calls_web_api_application_client_id}"; string api = $"api://{web_API_application_client_id}/{scope}"; // `session_state` があるとエラーになる。 //string code = $"{authorization_code}&session_state={web_app_calls_web_api_application_client_id}"; string code = $"{authorization_code}"; string grant_type = "authorization_code"; if (_httpClient == null) { _httpClient = new HttpClient(); } var parameters = new Dictionary<string, string>() { { "client_id", client_id }, { "api", api }, // "api"は、"" でも動作した。 { "code", code }, { "redirect_uri", redirect_uri }, { "grant_type", grant_type }, { "client_secret", client_secret }, }; var content = new FormUrlEncodedContent(parameters); HttpResponseMessage response = await _httpClient.PostAsync(url, content) .ConfigureAwait(false); Debug.WriteLine("-----------------------------------------"); Debug.WriteLine(response.Content.Headers.ContentType?.ToString()); Debug.WriteLine(response.Content.Headers.ContentType?.MediaType); Debug.WriteLine(response.Content.Headers.ContentType?.CharSet); Debug.WriteLine("-----------------------------------------"); foreach (var header in response.Headers) { string value = string.Join(",", header.Value); Debug.WriteLine($"{header.Key}: {value}"); } Debug.WriteLine("-----------------------------------------"); string responseContent = await response.Content.ReadAsStringAsync() .ConfigureAwait(false); Dictionary<string, object?>? responseJson = null; if (string.IsNullOrEmpty(responseContent) == false) { Debug.WriteLine(responseContent); responseJson = await response.Content.ReadFromJsonAsync<Dictionary<string, object?>>() .ConfigureAwait(false); if (responseJson != null) { foreach (var pair in responseJson) { Debug.WriteLine($"[{pair.Key}:{pair.Value}]"); } } } if (response.IsSuccessStatusCode == false) { throw new Exception("アクセストークンの要求に失敗しました。"); } return responseJson; } } ``` ### ナビゲーションメニューに追加する _Layout.cshtml ```htmlembedded= ・・・ <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> ・・・ <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/MsIdAccessTokenRequest">Microsoft ID アクセストークン要求</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/MsIdRedirect">Microsoft ID リダイレクト</a> </li> </ul> </div> ・・・ ``` ### 実行してみる ![](https://hackmd.io/_uploads/B1R0fwIc2.png) ![](https://hackmd.io/_uploads/r1Oy7P8c3.png) ![](https://hackmd.io/_uploads/ByeGmvUq2.png) ![](https://hackmd.io/_uploads/HyfNmDU5h.png) ![](https://hackmd.io/_uploads/HkEH7wIq2.png) ## アクセストークンを使用してWebAPIを呼び出すアプリ