tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

ASP.NET CoreのFacebook認証

f:id:tekitoumemo:20190105232452p:plain
前回、外部ログインのセットアップまでは説明したので今回はFacebook認証の追加だけです。

tekitoumemo.hatenablog.com

よく個人でサイトを作っている人を見るとFacebookは実装されてないことをよく見るのですが、ここは詰まる人が多いと思います。

前準備

Facebook for Developersでアプリを作成します。これもだいたい以下のURLに書いてます。
docs.microsoft.com

ここも基本的には「/signin-facebook」でcallback Urlを設定してください。
f:id:tekitoumemo:20190105232736p:plain
アプリIDとapp secretを取得しておいてください。

Facebookhttpsのみ対応しています。ssl入れてない人はあきらめましょう

Startupに登録

ConfigureServicesに以下を追加

services.AddAuthentication().AddFacebook(facebookOptions =>
{
    facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
    facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});

複数のSNSログインを実装している場合は以下のようにメソッドチェーンで実装出来ます。

services.AddAuthentication()
    .AddMicrosoftAccount(microsoftOptions => { ... })
    .AddGoogle(googleOptions => { ... })
    .AddTwitter(twitterOptions => { ... })
    .AddFacebook(facebookOptions => { ... });

Viewを作成

// これか
<a asp-action="SignIn" asp-route-provider ="Facebook">Facebook</a>
// これでもおけ
<a href="/auth/signin?provider=Facebook">Facebook</a>

これで前回のTwitterの記事と同じ要領で実装終了です。

本番だと動かない!

テストだと動くのですが、本番だと動きませんでした。要因はいくつかあると思うのですが、僕の場合はRedirectUrlがhttpで403になったことが原因でした。F12で調べてみてください。添付した画像はhttpsですが、403の場合httpになっている可能性があります。
f:id:tekitoumemo:20190105233552p:plain
「なんだよこれ」と思って自力でOAuthを実装しようかと思って気がつきました。

リクエストをhttpsにする

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
   return next();
});

これ以外にも以下の対処法があるみたいです。

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
     ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseHttpMethodOverride();

これはKestrelとリバプロが関係しているからXForwardedForで対処できるのかよくわかりません。よくわからなかったのでhttpsに変換する方法で対応しました(全部httpsでも困らないし)これがnginxだけなのかよくわかりません。

おそらくこれを対処しないとGoogleも出来ないですし、いつかTwitterもダメになっちゃうので気づいてよかったです。

ASP.NET CoreのTwitter認証と会員機能を作った理由

f:id:tekitoumemo:20190104232825p:plain
みんなの洋楽ランキングにSNS認証(TwitterFacebookGoogle)を追加しました。今回は外部ログインのセットアップとTwitter認証を説明をします。今回は2019年1月現在のやり方なので今後変わるかもしれません。

まずは審査から

最近は審査が厳しいので、以前書いた記事を参考にしてください。英語が全く出来ない僕でも審査が通ったので手順を踏めば誰でも取れるはずです。
tekitoumemo.hatenablog.com

.NET Coreの外部ログインのセットアップ

Twitter Developerを設定する

ほとんど以下に載っています。
docs.microsoft.com
2018/08からcallbackに設定しなければいけないので以下のように設定します。
f:id:tekitoumemo:20190104235030p:plain
デフォルトのcallback urlは「signin-twitter」です。これ間違えると403になります。またこのセットアップ方法を使う場合はhttpでリダイレクトするのでhttpにしないとエラーになります。僕は対策済みなのでhttpsですが、こちらは次回のFacebookログインで説明します。

Startupに登録

ConfigureServicesに以下を追加

services.AddAuthentication(options =>
{
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddTwitter(twitterOptions =>{
    twitterOptions.ConsumerKey = "your_apikey";
    twitterOptions.ConsumerSecret = "your_sercret_key";
});

AddAuthenticationはcookie 認証スキームにしています。ここら辺はすきな方法で良いかと思います。認証キーはhttps://developer.twitter.comから取得してください。

Configureに以下を追加

app.UseAuthentication();

認証用のControllerを作成

ログイン

IAuthenticationSchemeProviderをインジェクションします。

public class AuthController : Controller
{
        private IAuthServices _authServices;

        public AuthController (IAuthenticationSchemeProvider authenticationSchemeProvider) {
            _authenticationSchemeProvider = authenticationSchemeProvider;
        }
    public IActionResult Login()
    {
    }
}

Viewも作ります。

// これか
<a asp-action="SignIn" asp-route-provider ="Twitter">Twitter</a>
// これでもおけ
<a href="/auth/signin?provider=Twitter">Twitter</a>

サインイン

public IActionResult SignIn (string provider) {
    return Challenge (new AuthenticationProperties { RedirectUri = "/" }, provider);
}

providerは「Twitter」などSNSの名称が割り当てられます。RedirectUriはサインイン後のリダイレクト先でcallbackurlとは違うのでご注意を。

サインアウト

public async Task<IActionResult> SignOut () {
    await HttpContext.SignOutAsync (CookieAuthenticationDefaults.AuthenticationScheme);
    return RedirectToAction ("Index", "Home");
}

returnは適当に設定して下さい。

認証後の確認

以下で認証しているか確認できます。

Request.HttpContext.User.Identity.IsAuthenticated

さらに認証で取得した情報は以下で確認できます。

Request.HttpContext.User.Claims

TwitterだとEmailは取得できません。が、取得できる方法がありそうなので興味ある人は調べてみてはいかがでしょう。

認証後のそのほか

AuthorizeAttributeなどRoleベースの説明をすると長くなるのでこちらはまた別の機会で説明します。僕の場合は会員に権限(Role)をつけて、管理者のみ管理画面を使えるようにしています。

最後に会員認証を追加したきっかけを話します(自己満)

追加したきっかけ

自分たちで記事を書くのに限界を感じたので、ユーザーさんに記事を書いてもらいたいなぁと思いアンケートをとりました。アンケートの結果、記事を書いても良いと言う意見が100%(もちろん対価を得たいという意見が多いですが)だったので、ユーザーさんが記事をかける環境が必要でした。
f:id:tekitoumemo:20190104234001p:plain
Wikiみたく誰でもかける環境にしてもよかったのですが、自分の名前を出さない事により記事の質が下がる懸念と会員からユーザーのコミュニティに広げられそうな可能性を感じ認証を作りました。SNS認証のみ作成した理由は、メアド会員自体、時代に反している気がして僕自身SNS認証がないサイトは使いたくなかったです。ただ、やってるうちに必要か不必要か判断できるようになるので必要になったら追加しようと思います。

次回はFacebookログインを書きます
tekitoumemo.hatenablog.com

CoreTweetの検索をインジェクションで

.NETのTwitterライブラリにCoreTweetってのがあります。TwitterAPIのラッパーです。

www.nuget.org

最近、コンテンツ追加にCoreTweetを使ったので簡単な検索方法を説明します。以下のような感じで「続きを読む」を押すと過去5件でツイートが表示されます。
f:id:tekitoumemo:20190103214243p:plain


.NET Coreで実装したのでSeviceに登録してインジェクションするやり方です。

ダウンロード

dotnet add package CoreTweet --version 1.0.0.483

大枠を作成

// class
public class Twitter : ITwitter {
    private Tokens _tokens;
    public Twitter (string consumerKey, string consumerSecret, string accessToken, string accessSecret) {
        _tokens = Tokens.Create (consumerKey, consumerSecret, accessToken, accessSecret);
    }
}
// interface
public interface ITwitter { }

consumerKey、consumerSecret、accessToken、accessSecretはhttps://apps.twitter.com/から取得してください。interfaceは後から追加するので今は枠組みだけです。

Startup.csに登録

services.AddSingleton<ITwitter> (_ =>
   new Twitter (consumerKey, consumerSecret, accessToken, accessSecret));
}

これでインジェクションできる準備ができました。今回はシングルトンで問題ないのでシングルトンにしました。

検索メソッドを追加

public class Twitter : ITwitter {
    private Tokens _tokens;
    public Twitter (string consumerKey, string consumerSecret, string accessToken, string accessSecret) {
        _tokens = Tokens.Create (consumerKey, consumerSecret, accessToken, accessSecret);
    }
    // これ追加
    public async Task<SearchResult> SearchTweets (params Expression<Func<string, object>>[] parameters) {
        return await _tokens.Search.TweetsAsync (parameters);
    }
}
// interface
public interface ITwitter {
    Task<SearchResult> SearchTweets (params Expression<Func<string, object>>[] parameters);
}

Twitterは結構いろんなところで使いそうなのでinterfaceだけ定義してインジェクションして使えるようにしました。引数はExpression>にしてTweetsAsyncと全く同じように使えるようにします。

呼び出し側

public class TwitterRepository : ITwitterRepository {
    private ITwitter _twitter;
    public TwitterRepository (Twitter twitter) {
        _twitter = twitter;
    }
    public async Task SearchTweets (string keyword, long? maxId = null) {
        var tweets = await _twitter.SearchTweets (count => 100,
            q => keyword,
            max_id => maxId,
            lang => "ja",
            exclude => "retweets",
            tweet_mode => "extended");
    }
}

コンストラクタでインジェクションしてSearchTweetsで使ってます。オプションがいくつかあるので以下で説明します。以下のURLに全部乗ってます。
API reference index — Twitter Developers

max_id:twitterIdを指定するとtwitterIdより過去の記事を取得します。
lang:言語を指定します。jaは日本語です。
exclude:リツイートを除外します。僕は不要なんで必要なかったです。
tweet_mode:デフォルトだと140文字を取得できないので"extended"を指定して全部取れるようにします。

そのほか

これらはオプションであるかもしれません。

でかい画像に変換

ser.ProfileImageUrlで画像を取れるのですが、「normal.jpg」だと画像が小さいので「bigger.jpg」に置換しています。

User.ProfileImageUrl.Replace("normal.jpg", "bigger.jpg")
メタを置換

&は微妙なので

FullText.Replace("&amp;", "&")
本文にキーワードが含まれているものを抽出
Where(x => x.FullText.ToUpper().Contains(keyword.ToUpper())).

結構簡単に使えるので良いですね。次は.NET Coreでの認証を書きます。

ASP.NET Coreのinclude属性とexclude属性

ASP.NET Coreのタグヘルパーでinclude、exclude属性ってのがあります。これは、実行環境毎に定義するタグを切り替えられるもので開発、ステージング、本番など環境に応じて変更出来ます。僕の場合、BuildBundlerMinifierを使っているのでcssがミニファイされて開発時にいちいちビルドしなければいけなかったのですが、これのおかげで開発用のcss、本番用のcssと切り替えられるようになりました。今回は、本番でミニファイされたCSS、開発で生CSSを使うように説明します。

include属性

タグのinclude属性に表示されるための条件を設定します。

開発の場合

<environment include="Development">
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
</environment>

ステージング、本番の場合

<environment include="Staging,Production">
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true">
</environment>

ちなみにasp-append-version="true"は「v=Q55BkiaTp6uRl9...」をつけてくれるのでキャッシュ対策です。

BuildBundlerMinifier

BuildBundlerMinifierはこれです。Microsoft サポートが絡んでいるので信頼があります。BundlerMinifier.Coreってのがあるのですがこれは公式サポートがないので使う人は自己責任って感じですかね。
www.nuget.org
こんな感じでミニファイするファイルを指定するのですが、これがいちいちビルドしなければいけなかったのがめんどかったのでよかった。
f:id:tekitoumemo:20181228001107p:plain

マイクロソフトの公式はいまいち読む気がしないのですが、ちゃんと読まなきゃダメですね。

参考
バンドルし、縮小の ASP.NET Core で静的なアセット | Microsoft Docs

IEnumerable<T>をLINQでマッピング

小ネタ。

IEnumerableマッピングするときにAutoMapperを使うのがメジャーですが、AutoMapperを使いたくないなーってときがあります(僕だけ?)。例えば、自作ライブラリでnugetを使ってて、そのnugetライブラリのクラスとメインプロジェクトのクラスをマッピングするときなんかはわざわざnugetをインストールしたくありません(ちょっと何言ってんのかわかんない)。さらに.NET CoreでMiddlewareレベルでAutoMapperを使うようになったので、nugetライブラリをマッピングするのは気が引けます(僕だけ?)なのでLinqのselectを使ってマッピングする方法を説明します。

hogeクラスからpiyoクラスにマッピング

hogeクラスとpiyoクラスの中身
class hoge {
    public int hoge1 { get; set; }
    public int hoge2 { get; set; }
    public int hoge3 { get; set; }
}

class piyo {
    public string piyo1 { get; set; }
    public string piyo2 { get; set; }
    public string piyo3 { get; set; }
}
マッピング
var hoges = new List<hoge> () {
        new hoge () { hoge1 = 1, hoge2 = 2, hoge3 = 3 }
};

var piyos = hoges.Select (x => new piyo () {
        piyo1 = x.hoge1.ToString (),
        piyo2 = x.hoge2.ToString (),
        piyo3 = x.hoge3.ToString (),
});

まぁ、foreach使うよりはいいよねってレベルの内容。selectで複数指定出来ないからインスタンスにして返す、賢いなぁと思った

超絶厳しいTwitter APIが承認された

https://cdn-ak.f.st-hatena.com/images/fotolife/d/december1etk/20180727/20180727155141.png

Twitter APIを使いたいので申請したらいろいろとめんどくさかったんで備忘録として

最近の審査は結構厳しくなったみたいなので簡単な気持ちで申請したら思ったよりめんどくさかったんで一連の流れを記載します。ちなみに僕は英語が全く出来ませんが承認されたのでそんなに心配はいらないかと思います。

www.itmedia.co.jp

2018年9月7日 申請

申請したら返信がきました。

Thanks for applying for access!


In order to complete our review of your application, we need additional information about your use case. The most common types of information that can help expedite our review include:


The core use case, intent, or business purpose for your use of the Twitter APIs

If you intend to analyze Tweets, Twitter users, or their content, share details about the analyses you plan to conduct and the methods or techniques

If your use involves Tweeting, Retweeting, or liking content, share how you will interact with Twitter users or their content

If you’ll display Twitter content off of Twitter, explain how and where Tweets and Twitter content will be displayed to users of your product or service, including whether Tweets and Twitter content will be displayed at row level or aggregated


To provide this information to Twitter, reply to this email.


Thank you for your interest in building on Twitter.

ふむふむ(わからん)。

放置。。。

2018年12月20日 返信

とりあえず利用用途を答えろって言ってるっぽいんでそれなりの答えを返信した。

thank you for replying to my email.

On this site, Twitter's comments are posted using keywords such as song title and artist name.

For example, Twitter search with the keyword "Girls Like You" on the following URL, and place comments in real time.
https://mygkrnk.com/music/732

このURLでキーワードを使ってツイート検索するよ!って内容。

ものの30分ぐらいで返信が!

Thank you for your response.

At this time, we still do not have enough specific information about your intended use case to complete review of your application. As a reminder, we previously requested additional information about:

以下略

Twitter「マジで意味わかんねぇからもうちょっと詳しく説明してくんない?」

俺「。。。」

Quitaにいい記事が!
qiita.com

要はQ&A方式で返信すれば良いっぽい!
こんな感じで返信した。

Q1

> The core use case, intent, or business purpose for your use of the Twitter APIs
何に使うか教えろ

I will search the title of the song on Twitter
タイトル検索するわ

Q2

> If you intend to analyze Tweets, Twitter users, or their content, share details about the analyses you plan to conduct and the methods or techniques
どうやって使うんだ?

I use the following API to search the song title by twitter

GET https://api.twitter.com/1.1/search/tweets.json
このAPIを使うわ

Q3

> If your use involves Tweeting, Retweeting, or liking content, share how you will interact with Twitter users or their content.
ツイートとリツイートはどのような感じ※で使うんだ?
I have no plans to use Tweeting, Retweeting
ツイートとリツイートも使わん

※おそらくどんな内容でツイートするか聞いてるっぽい

Q4

> If you’ll display Twitter content off of Twitter, explain how and where Tweets and Twitter content will be displayed to users of your product or service, including whether Tweets and Twitter content will be displayed at row level or aggregated
集計?行レベル?とりあえず具体例教えれ

I am assuming to use it in the same way as the attached imag
添付した画像みて。

返信から30分後。。。

Your Twitter developer account application has been approved!

意外に簡単!とりあえず、簡潔に簡単に伝えれば良いっぽい。