アプリが Web API を呼び出すためのアクセストークンを取得するとき、OAuth 2.0 の Authorization Code Flow に対応することになると思う。
Web アプリなら、普通にリダイレクト先を用意すればいい。モバイルアプリでも、Custom URL Scheme によってアプリを起動できるので、それで認証コードを取り出せる。
問題はデスクトップアプリ。それもWinForms や WPF。一応、WebView を使い、ナビゲーションイベントを捕まえて認証コードを取り出す方法はある。ただ、ログインは内部ブラウザでは無く、外部ブラウザを使いたい。
あと、コンソールアプリも同様。リダイレクト先を http://localhost/ とかにしておいて、ブラウザのアドレスバーに表示される URL から認証コードを取り出す方法で、お茶を濁していた。
そんな中、Google の OAuth サンプルで、HttpListener を使ってリダイレクトを待ち受け、認証コードを取り出す方法を知った。何それ賢い。
github.com
GitHub の API でやってみた。
using System.Diagnostics;
using System.Net;
using System.Text;
using Octokit;
const string ClientId = "<Your Client ID>";
const string ClientSecret = "<Your Client Secret>";
const string RedirectUri = "http://localhost:8081/";
var github = new GitHubClient(new ProductHeaderValue("OAuthSample"));
var http = new HttpListener();
http.Prefixes.Add(RedirectUri);
Console.WriteLine("Listening..");
http.Start();
var loginUrl = github.Oauth.GetGitHubLoginUrl(
new OauthLoginRequest(ClientId)
{
RedirectUri = new Uri(RedirectUri),
State = Guid.NewGuid().ToString("N"),
Scopes =
{
"repo",
},
});
var escapedUrl = loginUrl.ToString().Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {escapedUrl}")
{
CreateNoWindow = true,
});
var context = await http.GetContextAsync();
var response = context.Response;
var responseString = "<html><body>Please return to the app.</body></html>";
var buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
await responseOutput.WriteAsync(buffer, 0, buffer.Length);
responseOutput.Close();
http.Stop();
var code = context.Request.QueryString.Get("code");
var oauthToken = await github.Oauth.CreateAccessToken(
new OauthTokenRequest(
ClientId,
ClientSecret,
code));
github.Credentials = new Credentials(oauthToken.AccessToken);
var repositories = await github.Repository.GetAllForCurrent();
foreach (var repository in repositories)
{
Console.WriteLine(repository.FullName);
}
Azure CLI なんかも、ソースコードを見たら az login では HTTP で待ち受けてるっぽかった。自分が知らなかっただけで、実はデファクトスタンダードだったりするんだろうか。