PKCE 対応に苦労したので、サンプルコードをメモしておく。
using System; using System.Diagnostics; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using IdentityModel; using IdentityModel.Client; namespace SampleApiClient { class Program { const string IdentityServerAddress = "IdentityServerのアドレス"; const string WebApiAddress = "Web API のアドレス"; const string ClientId = "クライアント ID"; const string ClientSecret = "クライアントシークレット"; const string Scope = "API スコープ"; // 認証に成功した後リダイレクトする URL const string RedirectUrl = "http://localhost"; static async Task Main(string[] args) { var tokenClient = new HttpClient { BaseAddress = new Uri(IdentityServerAddress), }; var disco = await tokenClient.GetDiscoveryDocumentAsync(); if (disco.IsError) { Console.WriteLine(disco.Error); return; } // PKCE 対応 var codeVerifier = CryptoRandom.CreateUniqueId(); string challenge; using (var sha256 = SHA256.Create()) { var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); challenge = Base64Url.Encode(challengeBytes); } // Web ブラウザでログインページを表示 // ログインに成功したあと、クライアントへの Web API 利用許可も行ったら、 // http://localhost にリダイレクトする。 // Web ブラウザのアドレスバーの URL から認証コードを取り出せる。 var nonce = CryptoRandom.CreateUniqueId(); var request = new RequestUrl(disco.AuthorizeEndpoint); var authorizeUrl = request.CreateAuthorizeUrl( clientId: ClientId, responseType: OidcConstants.ResponseTypes.Code, scope: Scope, redirectUri: RedirectUrl, nonce: nonce, codeChallenge: challenge, codeChallengeMethod: OidcConstants.CodeChallengeMethods.Sha256); var escapedUrl = authorizeUrl.Replace("&", "^&"); Process.Start(new ProcessStartInfo("cmd", $"/c start {escapedUrl}") { CreateNoWindow = true, }); // 認証コードを使ってアクセストークンを取得 Console.Write("AuthorizationCode:"); var code = Console.ReadLine(); var codeRequest = new AuthorizationCodeTokenRequest { Address = disco.TokenEndpoint, ClientId = ClientId, ClientSecret = ClientSecret, Code = code, RedirectUri = RedirectUrl, CodeVerifier = codeVerifier, }; var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync(codeRequest); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } // Web API を呼び出すクライアントは、 // アクセストークンを取得するクライアントとは別 var apiClient = new HttpClient { BaseAddress = new Uri(WebApiAddress), }; // アクセストークンを設定 apiClient.SetBearerToken(tokenResponse.AccessToken); // データを取得 var response = await apiClient.GetAsync( "/api/products"); response.EnsureSuccessStatusCode(); Console.WriteLine(await response.Content.ReadAsStringAsync()); Console.WriteLine("Enter で終了"); Console.ReadLine(); } } }