Edited at

ASP.NET CORE MVC + CentOS7 + httpd(Apache)でTwitter経由でoAuthログインしたい

こんにちわ。所属がサークル名になってるけど、FJ-WAN内にアカウントを持っている人です。

冬コミに向けて、こんなサービス作ったんですけど。

その時に掲題の件でクソほどハマったので、技術メモしときます。何かの参考になれば。


何がしたかったの?

TwitterのOAuthのAPIを叩いて、コールバックで戻って来たアカウントをDBに登録したかった。

つまり、右側のリンクをクリックすると、Twitterの認証に飛び、ユーザーが利用認証すると、自分のページにまた戻って来る、という仕組み。よくあるアレです。

ここを

image.png

こうして

image.png

こうじゃ!

image.png


環境・要件

環境と要件について書いておきます。


開発環境

項目
設定内容

OS
Windows10 Pro 1803

SDK
Visual Studio 2017 CE(会社のサブスクリプション使うと怒られるからね!!)

.Net Framework
.Net Core 2.0


本番環境

項目
設定内容

OS
CentOS 7.3(チェリーブロッサムインターネットのVPS)

SSL
TLSV1.2(証明書はLet's Encrypt先生を利用)

.Net Framework
.Net Core 2.0

Webサーバー
Apache(httpd リバースプロキシ) + Kestrel(ASP.NET CORE動作用)

DBサーバー
MySQL(わざわざMariaDBは殺した)


要件


  • MVCだよ。

  • SSLは必須(今日日https付いてないとかアカン)。

  • TwitterのoAuthを使ってログイン。サーバーにはユーザーシークレットやらパスワードやらは残さない。

  • FirefoxとCromeで見れればいい。IEはタヒんでくれ。


ハマったこと

ハマった内容は大まかに二つあります。

1.Twitterの認証に飛ぶのに、Buttonを使う例はクソほどあったが、リンクを使う例が箸にも棒にも掛からぬ

2.いざ、oAuth(と書いてナムサンと読む)!!とリクエストを飛ばしても403で返って来る

割と心が折れそうになりましたが、なんとか解決しましたと。

1,2と順番に書いていきます。


さてどうやってTwitterの認証に飛ばそうか

Formのボタンを使う例は公式を含めてクソほどある訳ですが。

僕のググり方が未熟なのか、どうもFormなんて面倒くさい事をせず、サクっとリンクで飛ばすやり方がわかりません。

ggrksとか言われそうですけど。いや、ググってますねんで。ホンマ。

で、1日くらい潰れたんですが。もう埒が明かないので、ソースサンプル見て自分で実装した。

結構案ずるより産むが易しでした。


RootController.cs

        [HttpGet("~/TwitterLogin")]

public IActionResult TwitterLogin()
{
try
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
return Challenge(new AuthenticationProperties { RedirectUri = "https://cloud.xn--k8j9a.jp/Twitter" }, "Twitter");
}
catch
{
return Redirect("~/InternalServerError");
}
}


こんなコントロールメソッドを用意してあげます。

デバッグの時はRedirectUriを自分のローカルデバッグ環境にしてあげてね。

で、VIEWにはこんな感じでよきにはらかう。


_Layout.cshtml

<p class="header_text">

<span class="header_text_twitter"><i class="fab fa-twitter"></i> @Html.Raw(ViewBag.AccessToken != null ? string.Format("<a href=\"https://twitter.com/{0}\" target=\"_blank\">{1}</a>&nbsp;でログイン中", ViewBag.TwitterID, ViewBag.TwitterDisplayName) : "<a href=\"/TwitterLogin\">Twitter経由でログインしてください</a>")</span>
</p>

cshtmlの中でゴリゴリ三項演算子使うと見づらいよね。ごめんね。

大事なところはここだけです。

<a href="/TwitterLogin">Twitter経由でログインしてください</a>

リンクでさっき作ったコントローラーにGetアクセスしてるだけです。簡単ね。難しく考えすぎたネ。


いざ、oAuth(ナムサン)!!……あれ?

ほいじゃぁまぁ、と。デバッグで動かしてリンククリックしてみます。

あれ、画面真っ白なんやが……という現象に遭遇します。

困った時はログを見ます。

dotnet-example: Connection id "0HLITC18U3J6L", Request id "0HLITC18U3J6L:00000001": An unhandled exception was thrown by the application.

dotnet-example: System.ArgumentException: The 'ConsumerKey' option must be provided.
dotnet-example: Parameter name: ConsumerKey
dotnet-example: at Microsoft.AspNetCore.Authentication.Twitter.TwitterOptions.Validate()
dotnet-example: at Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions.Validate(String scheme)
dotnet-example: at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.<InitializeAsync>d__42.MoveNext()

OOPS!!!

ConsumerKeyが見当たらないとな。ユーザーシークレットに書くだけじゃダメなんかい!!

ということで、appsettings.jsonあたりに書いてやります。


appsettings.json

{

"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"Authentication:Twitter:ConsumerKey": "イヤーーーーン",
"Authentication:Twitter:ConsumerSecret": "見ちゃらめぇ"
}

ConsumerKeyとConsumerSecretは自分の環境に置き換えて書いてあげてね。ボクのも見られたら困るので、適当な文字列に置き換えてあります。

気を取り直して、もう一度チャレンジです。

……やっぱり画面白いんやが。

まぁ、もう一度ログ見ましょうな。

dotnet-example: #033[41m#033[30mfail#033[39m#033[22m#033[49m: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[0]

dotnet-example: An unhandled exception has occurred: Response status code does not indicate success: 403 (Forbidden).
dotnet-example: System.Net.Http.HttpRequestException: Response status code does not indicate success: 403 (Forbidden).
dotnet-example: at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
dotnet-example: at Microsoft.AspNetCore.Authentication.Twitter.TwitterHandler.<ObtainRequestTokenAsync>d__12.MoveNext()

OOOOOOOOOOOOOOOPS!!!!

アイエエエエエエ!?ナンデ!?403(Forbidden)ナンデ!?

寝食を忘れてググりにググった。これでまた1日潰れた。

で、ようやく見つけたこちらの記事のここに、大事な事が書いてあった。

image.png


httpsを受け取ってhttpに投げ直す時にヘッダーが全部死にます.どういうことかというと,nginxがhttpsで受けた際には,"プロトコル=https;クライアント=133.5.6.44;リモートホスト:www.wallstudio.work;" みたいになっているのですが,Kestrelが受け取るのは"プロトコル=http;クライアント=127.0.0.1;リモートホスト:localhost;" みたいなことになってしまいます.性的なHTMLを返すだけのような場合には大して問題になりませんが,ユーザー認証を使っていて特にTwitterはCallbackURLが違うと403エラーで弾かれてしまいます.プログラムで正しいCallbackURLを生成できるように,ヘッダーを正しく転送してあげる必要があるのです.(地雷その2)


えっちな誤字には目を背けつつ。

なるほど、

Challenge(new AuthenticationProperties { RedirectUri = "https://cloud.xn--k8j9a.jp/Twitter" }, "Twitter");

でTwitterAPIを叩くときに、Kestrelくんは自分のURI(有体に言うと、http://localhost:5000 )でCallBackのURIを投げてしまうと。

うーんこの。やはりMS不能与(アタエランネーワ)

これ、どうにかして、Kestrelくんが認識している自分のURIをリバースプロキシ元のサーバールート(つまりhttps://cloud.こぁ.jp )にしてやらんといけないということです。

これ、Webサーバーがnginxの人はそのまま丸コピでイケるんちゃうの

しかし、わちきはApacheなのである!!!

参考にはなるが、丸コピはできぬのだ!!!!!つらい!!!!

ググってもApacheの場合のいいやり方が載っていない!!クソァ!!

でもここまできて諦めるのは。なんとかしたい。するしかない。

「閣下。今からApacheからnginxに乗り換えましょう。そうすればやり直しで工数は倍になりますが万事うまくいきます」

「んな事は理解ってんだよバーーーーカ!!時間が無ぇんだよ!!ファイッ嫌いだ!!」

(総統閣下画像略)

その試行錯誤で更に2日くらい潰れましたが。その過程はまあ要らんと思うので、結果だけ書きます。


RootController.cs

        [HttpGet("~/TwitterLogin")]

public IActionResult TwitterLogin()
{
try
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
// 追加はじまり
Request.Scheme = "https";
Request.Host = new HostString("cloud.xn--k8j9a.jp");
// 追加おわり
return Challenge(new AuthenticationProperties { RedirectUri = "https://cloud.xn--k8j9a.jp/Twitter" }, "Twitter");
}
catch
{
return Redirect("~/InternalServerError");
}
}

ここ。ここに注目。

Request.Scheme = "https";

Request.Host = new HostString("cloud.xn--k8j9a.jp");

オレオレ偽装です。なんてこった。ウカツ。実際ダサイ。

ま、まぁ、実現できれば方法は何だっていいのだ。

(なんかもっとスマートな方法があったら教えてくださいオナシャス)

こうすると、Kestrelくんは無事自分のプロトコルがhttpsでかつ、自分のホストアドレスがcloud.こぁ.jpだと思い込んでくれます。

これで、ようやくTwitterの認証が通って、~/Twitterのコールバックコントローラーに飛んできてくれるようになりましたとさ。めでたしめでたし。


タネ明石明かし

Twitterのアプリの設定画面のコールバックアドレスとKestrelくんが送り付けるURLが合っていないと、403になるようです。

RedirectUriにURLを設定しないと、/signin-twitterめがけてリダイレクトが飛ぶよんなどと公式ドキュメントにはありましたが。

これマジウソッパチ。結局RedirectUriに設定しても、/signin-twitterには飛ぶんですよ。一応。

その応答の中に更にRedirectUriに対するリダイレクトが仕込まれていて、最終的にRedirectUriに設定したところに飛ぶ、というのが正しい動き。

なので、Twitterの設定のベストプラクティスはこうなります。

image.png

signin-twitterは必ず入れること。でないと403になりますよと。ご注意。


大変参考になった記事