using System.Collections.Specialized;
    using System.Net;
    using System.Net.Http.Headers;
    using System.Security.Cryptography;
    using System.Text;
    using System.Text.Json;
    using System.Web;
    
    //==============================================================
    // Authorization Request / Response
    //==============================================================
    HttpClient Client = new HttpClient() { BaseAddress = new UriBuilder( "https://api.x.com" ).Uri, };
    //--------------------------------------------------------------
    // 認証用ハッシュ値の生成
    //--------------------------------------------------------------
    string CodeVerifier;
    using( var rng = RandomNumberGenerator.Create() )
    {
        var buff = new byte[64];
        rng.GetBytes( buff );
        CodeVerifier = Convert.ToBase64String( buff ).Replace( "=", "_" ).Replace( "+", "-" ).Replace( "/", "_" );
    }
    CodeVerifier = HttpUtility.UrlEncode( CodeVerifier );
    string SndState = CreateUniqueCode();
    Challenge = GetHash256( CodeVerifier );
    //--------------------------------------------------------------
    // ログイン接続URLの生成
    //--------------------------------------------------------------
    Dictionary<string, string> QueryDict = new Dictionary<string, string>() {
        { "response_type", "code" },
        { "client_id", "$(クライアントID)" },
        { "redirect_uri", "$(コールバックURL)" },
        { "scope", string.Join( " ", {"tweet.read", "tweet.write", "users.read"} ) },
        { "state", SndState },
        { "code_challenge", CodeVerifier },
        { "code_challenge_method", "plain" }, // S256で試しましたがHTTPで失敗します。
    };
    UriBuilder UrlBldr = new UriBuilder( "https://twitter.com/i/oauth2/authorize" ) { Query = ConvertParamToQuery( QueryDict ), };
    
    /* ユーザー認証はAPI(エンドポイント)ではない事に注意して下さい。必ず、WebでのURLアクセスが要ります。 */
    bool BrowserSupport = await Browser.Default.OpenAsync( UrlBldr.Uri );
    //--------------------------------------------------------------
    // コールバック受付開始
    //--------------------------------------------------------------
    Dictionary<string, string> RcvDataDict = await ResponseWait( new string[] { "state", "code" } ); // code①
    if( !SndState.Equals( RcvDataDict["state"] ) )
    {
        /* 外部からの攻撃対策です。なりすますのかな? */
        return;
    }
    
    //==============================================================
    // Access Token Request / Response
    //==============================================================
    //--------------------------------------------------------------
    // HTTPヘッダー
    //--------------------------------------------------------------
    string Client_OAuth = Convert.ToBase64String( Encoding.UTF8.GetBytes( "$(クライアントID)" + ":" + "$(クライアントシークレット)" ) );
    Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Client_OAuth );
    Client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/x-www-form-urlencoded" ) );
    Client.DefaultRequestHeaders.UserAgent.Add( new ProductInfoHeaderValue( new ProductHeaderValue( AppEnvironment.APP_NAME ) ) );
    //--------------------------------------------------------------
    // HTTPコンテンツ
    //--------------------------------------------------------------
    Dictionary<string, string> BodyDict = new Dictionary<string, string>() {
        { "code", RcvDataDict["code"] },
        { "grant_type", "authorization_code" },
        { "redirect_uri", $(コールバックURL) },
        { "code_verifier", CodeVerifier },
    };
    var HttpBody = new FormUrlEncodedContent( BodyDict );
    //--------------------------------------------------------------
    // HTTPリクエスト
    //--------------------------------------------------------------
    using( HttpResponseMessage Response = await Client.PostAsync( "/2/oauth2/token", HttpBody ).ConfigureAwait( true ) )
    {
        /* Responseの戻り値でHTTPステータスが200番台なら成功(ifしてね) */
        var ResponseBody = await Response.Content.ReadAsStringAsync();
        Access_Token = JsonDocument.Parse( ResponseBody ).RootElement.GetProperty( "access_token" ).GetString();
        Refresh_Token = JsonDocument.Parse( ResponseBody ).RootElement.GetProperty( "refresh_token" ).GetString();
    }
    //**************************************************************
    #region #### コールバック受付
    //**************************************************************
    private async Task<Dictionary<string, string>> ResponseWait( string[] RequestParams )
    {
        Dictionary<string, string> ResponseParams = new Dictionary<string, string>();
    
        using( HttpListener RcvListen = new HttpListener() )
        {
            /* コールバックURLの最後の文字は「/」で終わる必要があるみたいです。 */
            RcvListen.Prefixes.Add( "$(コールバックURL)" + "/" );
            RcvListen.Start();
            var context = await RcvListen.GetContextAsync().ConfigureAwait( true );
            foreach( string RequestParam in RequestParams )
            {
                ResponseParams.Add( RequestParam, context.Request.QueryString.Get( RequestParam ) );
            }
            RcvListen.Stop();
        }
    
        return ResponseParams;
    }