質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.12%

FlaskでOAuth認証時に発生するUnicodeDecodeErrorを解決したい

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 504

masty0000

score 4

前提・実現したいこと

PythonのWEBフレームワークFlaskを用いて、とある書籍を参考に、質問箱アプリを開発しています。
ログイン機能として、Twitterのアカウントを用いたOAuth認証を実装しようとしています。

リクエスト・トークンを取得し、その後リクエスト・トークンを元にアクセス・トークンを取得する段階で下記のようなエラーが発生しました。
(WEB画面上で言うと、ユーザーに対し、アプリがユーザー情報を利用することを許可する画面から、許可後、コールバックURLへリダイレクトするところでエラーが発生)

発生している問題・エラーメッセージ

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 1490: ordinal not in range(128)

表示されたメッセージの全文としては、以下の通りです。

(flaskApp3) mas-mac-pc% flask run
 * Serving Flask app "app.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 916-800-986
127.0.0.1 - - [30/Dec/2018 16:11:21] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [30/Dec/2018 16:11:25] "GET /oauth/twitter HTTP/1.1" 302 -
127.0.0.1 - - [30/Dec/2018 16:11:28] "GET /oauth/twitter/callback?provider=twitter&oauth_token={非公開}&oauth_verifier={非公開} HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/Mas/Python/flask/flaskApp3/app.py", line 123, in oauth_callback
    data={'oauth_verifier': request.args['oauth_verifier']}  # ここがエラーの原因
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/rauth/service.py", line 359, in get_auth_session
    **kwargs)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/rauth/service.py", line 332, in get_access_token
    process_token_request(r, decoder, key_token, key_token_secret)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/rauth/service.py", line 20, in process_token_request
    data = decoder(r.content)
  File "/Users/Mas/.local/share/virtualenvs/flaskApp3-s8OM8UNK/lib/python3.7/site-packages/rauth/utils.py", line 26, in parse_utf8_qsl
    for k, v in dict(parse_qsl(s)).items():  # pragma: no cover
  File "/anaconda3/lib/python3.7/urllib/parse.py", line 683, in parse_qsl
    qs, _coerce_result = _coerce_args(qs)
  File "/anaconda3/lib/python3.7/urllib/parse.py", line 123, in _coerce_args
    return _decode_args(args) + (_encode_result,)
  File "/anaconda3/lib/python3.7/urllib/parse.py", line 107, in _decode_args
    return tuple(x.decode(encoding, errors) if x else '' for x in args)
  File "/anaconda3/lib/python3.7/urllib/parse.py", line 107, in <genexpr>
    return tuple(x.decode(encoding, errors) if x else '' for x in args)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 1490: ordinal not in range(128)

該当のソースコード

from flask import Flask, redirect, url_for, session, request, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

from flask_login import UserMixin, LoginManager, login_user, logout_user, current_user, login_required

from datetime import datetime
from rauth import OAuth1Service

app = Flask(__name__)

app.secret_key = 'xxxxxxxxxx'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://localhost/testdb"
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'index'
migrate = Migrate(app, db)

service = OAuth1Service(
    name='twitter',
    consumer_key='XXXXXXXXXX',
    consumer_secret='XXXXXXXXXX',
    request_token_url='https://api.twitter.com/oauth/request_token',
    authorize_url='https://api.twitter.com/oauth/authorize',
    access_token_url='https:/api.twitter.com/oauth/access_token',
    base_url='https://api.twitter.com/1.1'
)

# Userモデル
class User(UserMixin, db.Model):
    # テーブル定義
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    description = db.Column(db.String(1204), index=True, unique=True)
    user_image_url = db.Column(db.String(1204), index=True, unique=True)
    date_published = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    twitter_id = db.Column(db.String(64), nullable=False, unique=True)

    def __repr__(self):

        return '<User %r>' % self.username

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))

@app.route('/oauth/twitter')
def oauth_authorize():
    if not current_user.is_anonymous:
        return redirect(url_for('index'))
    else:
        request_token = service.get_request_token(
            params={'oauth_callback': url_for('oauth_callback', provider='twitter', _external=True)}
        )

        session['request_token'] = request_token

        return redirect(service.get_authorize_url(request_token[0]))

@app.route('/oauth/twitter/callback')
def oauth_callback():
    request_token = session.pop('request_token')

    oauth_session = service.get_auth_session(
        request_token[0],
        request_token[1],
        method='GET', # 自分で追加
        data={'oauth_verifier': request.args['oauth_verifier']}  # ここがエラー発生
    )

    profile = oauth_session.get('account/verify_credentials.json').json()

    twitter_id = str(profile.get('id'))
    username = str(profile.get('name'))
    description = str(profile.get('description'))
    profile_image_url = str(profile.get('profile_image_url'))
    user = db.session.query(User).filter(User.twitter_id==twitter_id).first()

    if user:

        user.twitter_id = twitter_id
        user.username = username
    else:

        user = User(twitter_id=twitter_id,
                    username=username,
                    description=description,
                    user_image_url=profile_image_url)
        db.session.add(user)
    db.session.commit()

    login_user(user, True)
    return redirect(url_for('index'))

@login_manager.user_loader
def load_user(id):
    return User.query.get(int(id))

試したこと

エラーメッセージからasciiでデコードしているようなので、
以下を実行してみましたが、環境のエンコーディングはutf-8でした。

>>> import locale
>>> print(locale.getpreferredencoding())
UTF-8
>>> import sys
>>> print(sys.getdefaultencoding())
utf-8


また、エラーメッセージからparse.pyで例外が発生しているようだったので、
parse.pyの中身を見てみたりしてみましたが、いまいちわからず・・・

補足情報(FW/ツールのバージョンなど)

・OS:OS X EI Captain(Version 10.11.6)
・Python:3.7.0
・Flask:1.0.2
・pipenv:version 2018.11.14

追記

上記app.py中の下記部分request.args['oauth_verifier']の型はstrです。

data={'oauth_verifier': request.args['oauth_verifier']}  # ここがエラー発生
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • hayataka2049

    2018/12/30 18:56

    すみません、質問文の3.7という記載を見落としていました。スルーして構いません

    キャンセル

  • hayataka2049

    2018/12/30 18:56

    すみません、質問文の3.7という記載を見落としていました。スルーして構いません

    キャンセル

  • masty0000

    2018/12/30 19:00

    ありがとうございます。
    念の為再度確認してみましたが、以下の通りでした。
    3.7.0 (default, Jun 28 2018, 07:39:16)
    [Clang 4.0.1 (tags/RELEASE_401/final)]

    キャンセル

回答 1

+2

FlaskもOAuth認証もよく分からないのですが、エラー内容で検索するとrequest.args uncompatible with quote, which lead decode error #2016が引っかかってきました。
これによるとrequest.url_charsetasciiが入っていると内部でこのエンコーディングが利用され、エラー発生しているのかもしれません。
そうだとするとrequest.url_charset='utf-8'した後に関数を呼び出すと正常に動作するかもしれません。

あるいは以下のようにしてbyteのまま取得できれば、自力でデコードすることで処理できるかもしれません。

ov = request.args.get('oauth_verifier',type=byte) # byteのまま取得できないか?
ov = ov.decode('utf-8')
print(ov) # 念のため意図したものか確認
# ovはURLデコードも必要?

oauth_session = service.get_auth_session(
        # 略
        data={'oauth_verifier': ov}


参考:Flaskのrequest.argsでパラメータの処理について

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る