.NET の OAuth クライアントは IdentityModel が定番だけど、あえて IdentityModel を使わず、HttpClient だけで OAuth2.0 の認可コードフローを通す。PKCE にも対応.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace SampleWebApiClient
{
class Program
{
const string IdentityServerAddress = "IdentityServerのアドレス";
const string WebApiAddress = "Web API のアドレス";
const string ClientId = "クライアント ID";
const string Scope = "API スコープ";
const string RedirectUri = "リダイレクト URI";
static async Task Main(string[] args)
{
string codeVerifier;
using (var rng = RNGCryptoServiceProvider.Create())
{
var bytes = new byte[32];
rng.GetBytes(bytes);
codeVerifier = Convert.ToBase64String(bytes)
.Split('=')[0]
.Replace('+', '-')
.Replace('/', '_');
}
string challenge;
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
challenge = Convert.ToBase64String(challengeBytes)
.Split('=')[0]
.Replace('+', '-')
.Replace('/', '_');
}
var authorizeUrl =
$"{IdentityServerAddress}/connect/authorize" +
"?client_id=" + ClientId +
"&response_type=code" +
"&scope=" + Uri.EscapeUriString(Scope) +
"&redirect_uri=" + Uri.EscapeUriString(RedirectUri) +
"&code_challenge=" + challenge +
"&code_challenge_method=S256"; ;
var escapedUrl = authorizeUrl.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {escapedUrl}")
{
CreateNoWindow = true,
});
Console.Write("AuthorizationCode:");
var code = Console.ReadLine();
var tokenClient = new HttpClient
{
BaseAddress = new Uri(IdentityServerAddress),
};
var tokenResponse = await tokenClient.PostAsync(
"/connect/token",
new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = code,
["client_id"] = ClientId,
["redirect_uri"] = RedirectUri,
["code_verifier"] = codeVerifier,
}));
var tokenResponseBody = await tokenResponse.Content.ReadAsStringAsync();
if (!tokenResponse.IsSuccessStatusCode)
{
Console.WriteLine(tokenResponseBody);
goto END;
}
var accessToken = JsonDocument.Parse(tokenResponseBody)
.RootElement
.GetProperty("access_token")
.GetString();
var apiClient = new HttpClient
{
BaseAddress = new Uri(WebApiAddress),
};
apiClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
var response = await apiClient.GetAsync(
"/api/products");
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
END:
Console.WriteLine("Enter で終了");
Console.ReadLine();
}
}
}