いくつかTwitter認証を使うWebApplicationを作る過程でTwitterでの認証をテンプレート化させコピペで作れるようにしたのでそれをご紹介します。
この記事で出来るようになること
- Twitter認証機能を持ったExpressAppをサクッと作れるようになる
- WebAppとしても,ネイティブ使うAPIエンドポイントとしても使える
- (後日出来るようになるかも) Swift4でのiOSネイティブからの当APIの使い方がサクッとわかる
前提
コピペで作るためには以下を満たしておく必要があります。
-
TwitterAppを作成できる
- 以前にTwitterDeveloperの大規模な変更があって、TwitterAPPを作るためには作文を書かなきゃいけなかった気がします。
- TwitterDeveloperにアクセスしてAppのリストとCreate an app ボタンが表示されればOK.
express-generatorが導入されている
Expressプロジェクトをコマンド一つで生成してくれる便利な奴
導入とか(公式)PM2が導入されている
ProcessManager(多分)
nodeプロジェクトを手軽にデーモン化させられる奴(起動/ホットリロード/クラッシュ時の再起動/startup等々)
導入とか(公式)-
ドメインが用意されていてWebAppを公開できるようになっている
長くなるので省きますが、https://xxx.your.domain/ とかにアクセスして生成したてのExpressを起動した際にExpress Welcome to Express
が表示されればOK.
今回はhttps://qiita.your.domain/
というURLでAPIを公開してみたいと思います。
TwitterAppを作成
twitterDeveloperからCreate an appを押し、App作成画面へ移ります。
必要事項を記述し、
Allow this application to be used to sign in with Twitter
項目のEnable Sign in with Twitter
にチェックを入れます。Callback URLs
項目がアクティブになるので、ここにhttps://qiita.your.domain/auth/twitter/callback
を入力します。
※ /auth/twitter/callbackの部分は本来任意ですが、これを変更する場合後述のルーティングのコードも変更する必要があります。項目記述後、流れに沿ってAppを作成し最終的に自分のAppsDashboardに作成したAppが表示されていれば準備完了。
AppsDashboardからAppを開き、keys and tokensのConsumer API Keysを確認できるようにしておきます。
実装
Express
まずはExpressを初期化します。
$ express -e qiita-twitter
$ cd qiita-twitter
$ npm i
次にpm2を初期化します
$ pm2 init
$ vi ecosystem.config.js
pm2
pm2の設定を変更します。
module.exports = {
apps: [
{
name: 'qiita-twitter',
script: 'npm',
args: 'start',
watch: true,
env: {
PORT: 3000,
HOST_NAME: 'qiita.your.domain',
USE_SSL: true,
AUTH: {
twitter: {
active: true,
CONSUMER_KEY: 'Your CONSUMER_KEY',
CONSUMER_SECRET: 'Your CONSUMER_SECRET',
},
}
}
}
]
};
nameはpm2上で管理するApp名です。
pm2 logs qiita-twitter
とかするとログを見れたりします。
scriptとargsは起動時のコマンドです。
$ npm start
で起動するので、このように記述します。
watchはホットリロード(ファイルを変更すると自動再起動してくれるやつ)を使うか否かです。
trueにすると起動ディレクトリ以下全てのファイルを、trueの代わりに['routes', 'models']みたいにするとroutesとmodelsディレクトリ以下を監視してくれるようです。
で、重要なのがenvです。
USE_SSLとかは後ほどコールバックURLを組み立てるときに使っていますが、そこでURLを直打ちしてしまえば必要ありません。
AUTH=>twitter=>activeみたいになってるのは、他にもlocal認証(よくあるメアドとPWの認証)やFaceBook認証とか使う時に分かるやすくなるメリットがあります。
この辺はコードと相談しながらお好みで変更しましょう。
gitで管理する場合ecosystem.config.jsはgitignoreに突っ込むなりしてgit監視下から外しましょう
個人的にはecosystem.config.js.sampleというenvの値を空白にしたものを用意しておき、これをgitに上げておくのがベターかなと思います。
休憩
一先ずここで起動確認をしておきましょう。
$ pm2 start ecosystem.config.js
$ pm2 logs qiita-twitter
適当なブラウザでhttps://qiita.your.domain
にアクセスして
Express
Welcome to Experss
が表示されていればOKです。
Twitter認証
いよいよTwitter認証のroutingを実装します。
以下個人的に使っているディレクトリ構成となります。
お試す前にコードを読んで各自改変することをお勧めします。
(contorllers内でrouting設定しちゃってたりと健康被害を及ぼす可能性があります)
packageインストール
$ npm i -S express-session passport passport-twitter
ルーティング
$ mkdir controllers
$ touch controllers/authController.js
$ touch controllers/userController.js
const passport = require('passport');
const TwitterStrategy = require('passport-twitter').Strategy;
const uc = require('./userController');
module.exports = {
initialize: function(app) {
this.app = app;
this.authSettings = JSON.parse(process.env.AUTH);
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(uc.userSerialize);
passport.deserializeUser(uc.userDeserialize);
this.twitterActivate();
},
twitterActivate: function() {
if(!this.authSettings.twitter.active) {
console.log('Twitter Auth is not activated');
return;
}
let consumerKey = this.authSettings.twitter.CONSUMER_KEY;
let consumerSecret = this.authSettings.twitter.CONSUMER_SECRET;
let callbackURL = process.env.USE_SSL ? 'https' : 'http';
callbackURL += '://';
callbackURL += process.env.HOST_NAME;
callbackURL += callbackURL.endsWith('/') ? '' : '/';
callbackURL += 'auth/twitter/callback';
passport.use(new TwitterStrategy({
consumerKey: consumerKey,
consumerSecret: consumerSecret,
callbackURL: callbackURL
}, uc.twitterAuth));
this.app.get('/auth/twitter', passport.authenticate('twitter'));
this.app.get('/auth/twitter/callback', passport.authenticate('twitter'), async function(req, res) {
const user = req.session.passport.user;
res.json(user);
});
console.log('Twitter Auth is activated');
},
}
const ac = require('./authController');
module.exports = {
twitterAuth: async function(token, tokenSecret, profile, done) {
//本来ここでユーザー検索して、存在したらそのObjectを返す
//存在しなければ新規ユーザー作成
//みたいなことをする。
//tokenとtokenSecretそのままobjectに格納してログイン状態として扱うのは良くないです。
const userObj = {
twitterId: profile.id,
screenName: profile.username,
token: token,
tokenSecret: tokenSecret
};
//ログ表示もいくない。
console.log(userObj);
done(null, userObj);
},
userSerialize: async function(user, done) {
done(null, user);
},
userDeserialize: async function(id, done) {
console.log('user deserializing');
console.log(id);
done(null, id);
},
};
app.jsから読み込み/初期化
$ vi app.js
最後にapp.jsを編集し、読み込み・初期化を行います。
//最魚のrequire群でsessionとauthControllerを読み込み(6行目くらい)
const session = require('express-session');
const authController = require('./controllers/authController');
//requireしたモジュール系をuseしている部分でsessionの読み込みをしてからauthControllerをinitialize(順番大事)を追加(23行目くらい)
app.use(session({
secret: 'kokoikeapi',
resave: false,
saveUninitialized: true
}));
authController.initialize(app);
動作確認
最後にコールバックしてきたら、ユーザーのTweet一覧を表示してみましょう.
さらに体に良くないですが、authController内でTwitterモジュールを読み込んでお試し表示してみましょう。
$ npm i -S twitter
//4行目くらいでrequireしておきます。
const Twitter = require('twitter');
// 36行目くらいの/auth/twitter/callbackのルーティングの処理の中身を変更
const user = req.session.passport.user;
const consumer = JSON.parse(process.env.AUTH).twitter;
const client = new Twitter({
consumer_key: consumer.CONSUMER_KEY,
consumer_secret: consumer.CONSUMER_SECRET,
access_token_key: user.token,
access_token_secret: user.tokenSecret
});
const params = { screen_name: user.screenName};
client.get('statuses/user_timeline', params, function(err, tweets, response) {
if(!err) {
res.json(tweets);
} else {
res.json(err);
}
});
//res.json(user);
これで認証が終わってcallbackしてくると自分のツイート一覧のjsonがresponseに流れます。
本来の使い方としては、userController時点でuserデータを作成し、doneにそのuserデータを渡しておきます。
すると、authControllerで取得するuserにはidが含まれているので、これを使ってユーザーのプロフィールページやトップページにリダイレクトするといいと思います。
this.app.get('/auth/twitter/callback', passport.authenticate('twitter'), async function(req, res) {
//トップへリダイレクトする場合
//res.redirect('/');
//user個別ページへリダイレクトする
const user = req.session.passport.user;
res.redirect('/users/' + user.id);
//みたいな。
});