アプリが Web API を呼び出すためのアクセストークンを取得するとき、OAuth 2.0 の Authorization Code Flow に対応することになると思う。
Web アプリなら、普通にリダイレクト先を用意すればいい。モバイルアプリでも、Custom URL Scheme によってアプリを起動できるので、それで認証コードを取り出せる。
問題はデスクトップアプリ。それもWinForms や WPF。一応、WebView を使い、ナビゲーションイベントを捕まえて認証コードを取り出す方法はある。ただ、ログインは内部ブラウザでは無く、外部ブラウザを使いたい。
あと、コンソールアプリも同様。リダイレクト先を http://localhost/ とかにしておいて、ブラウザのアドレスバーに表示される URL から認証コードを取り出す方法で、お茶を濁していた。
そんな中、Google の OAuth サンプルで、HttpListener を使ってリダイレクトを待ち受け、認証コードを取り出す方法を知った。何それ賢い。
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")); // リダイレクトで認証コードを受け取るために、 // HttpListener を使って待ち受ける。 var http = new HttpListener(); http.Prefixes.Add(RedirectUri); Console.WriteLine("Listening.."); http.Start(); // GitHub のログインページを表示。 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, }); // OAuth 認証のレスポンスを返すために待機 var context = await http.GetContextAsync(); // ブラウザに HTTP レスポンスを送る 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 の API を呼び出す 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 で待ち受けてるっぽかった。自分が知らなかっただけで、実はデファクトスタンダードだったりするんだろうか。