概要
reactにおける認証は下記のページなどで行っている人が複数人いる。
React Authentication with Twitter, Google, Facebook and Github
ReactでSPAを作り、Twitter認証(OAuth)でログインする。バックエンドはRails
ただ、自分は馬鹿なのでいまいちよくわからなかった(後、上記ページはサーバーサイドがexpressとかRailsでflaskは見つからなかった)。
そのため、小手先でめっちゃ強引な認証を書いた。
WebSocket使うのもReactのuseEffect使うのも、Flask使うのも初めてなので、参考程度にみてもらえると良い(後々、ちゃんとしたコードで書き直したい)
流れ
今回は「連携アプリ認証」のボタンを押すと連携が始まるようになっているので、そこの部分は適当に変えて欲しい。
- 認証を行うボタンをおす(js内handleClick関数)
- サーバーに"Twitter"の文字列が飛ぶ(js内handleClick関数)
- URLをサーバーが返す(Twitter認証用)(python内pipe関数)
- jsでURLで移動(js内ws.onmessage = function(e)内)
- Twitter認証画面
- 認証する
- クライアント側のページにredirect
- サーバーにauth_tokenとauth_verifierの情報がいく(useEffect内ws.onopenより)
- サーバーはtokenとverifierを使ってaccess_token_secretを作成する(python内user_timeline)
- Twitterから認証ユーザの最新のタイムライン一件を持ってくる(python内user_timeline)
認証などにはtweepyを用いている。
実際のコード
index.py
import os
import json
import sys
import tweepy
from flask import Flask, session, redirect, render_template, request, jsonify, abort, make_response
from flask_cors import CORS, cross_origin
from os.path import join, dirname
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
app = Flask(__name__)
CORS(app)
#勝手に決める
app.secret_key = ""
#TwitterAppから持ってくる
CONSUMER_KEY = ""
CONSUMER_SECRET = ""
@app.route('/pipe')
def pipe():
if request.environ.get('wsgi.websocket'):
ws = request.environ['wsgi.websocket']
while True:
message = ws.receive()
# print(message)
#ボタンを押すとTwitterがwebsocketで送られるので送られたら発火
if message == "Twitter":
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
try:
# 連携アプリ認証用の URL を取得
redirect_url = auth.get_authorization_url()
session['request_token'] = auth.request_token
except Exception as ee:
# except tweepy.TweepError:
sys.stderr.write("*** error *** twitter_auth ***\n")
sys.stderr.write(str(ee) + "\n")
#websocketでurlを送り返す
ws.send(redirect_url)
ws.close()
#return無いとエラーが出るため
return redirect_url
elif message != None:
messages = json.loads(message)
# print(messages)
user_timeline(messages, ws)
def user_timeline(auths, ws):
# tweepy でアプリのOAuth認証を行う
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
verifier = str(auths["oauth_verifier"])
# Access token, Access token secret を取得.
auth.request_token['oauth_token'] = str(auths["oauth_token"])
auth.request_token['oauth_token_secret'] = verifier
try:
access_token_secret = auth.get_access_token(verifier)
except Exception as ee:
print(ee)
return ""
print(access_token_secret)
# tweepy で Twitter API にアクセス
api = tweepy.API(auth)
# user の timeline 内のツイートのリストを1件取得して返す
for status in api.user_timeline(count=1):
text = status.text
# user の timeline 内のツイートのリストを1件取得して返す
ws.send(text)
ws.close()
def main():
app.debug = True
server = pywsgi.WSGIServer(("", 5000), app, handler_class=WebSocketHandler)
server.serve_forever()
if __name__ == "__main__":
main()
app.js
import React from 'react';
import './App.css';
import { useState, useEffect } from 'react';
function App() {
const [flag, setFlag] = useState(false);
const [userData, setUserData] = useState('');
const [data, setData] = useState('');
//webSocketとの通信
const ws = new WebSocket('ws://localhost:5000/pipe');
// レンダー前にwsがopenした後にurl内のverifierを返す
useEffect(() => {
ws.onopen = event => {
if (userData == false && window.location.search.includes('verifier')) {
setFlag(true);
ws.send(getParam('oauth_verifier'));
}
};
setUserData('true');
});
//url内の特定の要素を持ってくるためのコード
function getParam(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
// サーバー側からメッセージが送られてきた際に受け取り、関数を発動する(Twitterの認証用URLに飛ぶため)
ws.onmessage = e => {
console.log(e);
if (e.data.includes('oauth_token')) {
window.location.href = e.data;
} else {
console.log(e);
setData(e.data);
}
};
//クリックした時(今回は文字をbuttonをクリックしたらサーバーにTwitterのもじが送られる)
function handleClick() {
console.log('rest');
ws.send('Twitter');
}
console.log(flag);
//レンダー要素の切替
let render = flag ? (
<div>{data}</div>
) : (
<button onClick={handleClick}>連携アプリ認証</button>
);
return <>{render}</>;
}
export default App;
正直、めっちゃ適当なコードなので参考にできればする程度がちょうどいいと思う(後、websocketのエラーが出る。closedの状態で通信を行ってしまっているためだと思うので修正できたら編集します)