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;
}