
【第3回】FastAPIチュートリアル: toDoアプリを作ってみよう【認証・ユーザ登録編】
2021.12.20
FastAPIチュートリアル: toDoアプリを作ってみよう~第3回~
前回の記事「【第2回】FastAPIチュートリアル: ToDoアプリを作ってみよう【モデル構築編】」の続きです。
今回も、ToDoアプリを作る過程で、FastAPIの使い方を学んでいきましょう!
第1回はこちら
認証
FastAPIでは、HTTP BasicAuth(いわゆるベーシック認証)とOAuth2(オーオース)と呼ばれる認証をサポートしています。
本アプリケーションは、あまり凝ったアプリケーションでもないので、ベーシック認証を利用することにします。
今の時代、「ログインフォームなどを作成してログイン」する形のほうが一般的ではありますが、今回はご容赦ください。
さて、FastAPIでベーシック認証を使用するにあたり、下準備がいくつかあります。
新たにimportするモジュール
controller.py に、以下のモジュールを新しくインポートしてください。
(重複しているものは、適宜無視してください)
1 2 3 4 5 6 7 8 9 10 11 | from fastapi import FastAPI, Depends, HTTPException # new from fastapi.security import HTTPBasic, HTTPBasicCredentials # new from starlette.templating import Jinja2Templates from starlette.requests import Request from starlette.status import HTTP_401_UNAUTHORIZED # new import db # new from models import User, Task # new import hashlib # new |
上から2つは、FastAPIでベーシック認証を使うのに必要なクラスです。
後半の新しく追加したものは、入力された「ユーザ名」や「パスワード」がデータベースにあるものと一致するかを検証するのに使います。
管理者ページのコントローラを修正する
次に、管理者ページのビューを表示する前のコントローラ admin() を修正していきます。
というより、ほとんど変更しますので、上からコピペでも構いません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def admin(request: Request, credentials: HTTPBasicCredentials = Depends(security)): # Basic認証で受け取った情報 username = credentials.username password = hashlib.md5(credentials.password.encode()).hexdigest() # データベースからユーザ名が一致するデータを取得 user = db.session.query(User).filter(User.username == username).first() task = db.session.query(Task).filter(Task.user_id == user.id).all() if user is not None else [] db.session.close() # 該当ユーザがいない場合 if user is None or user.password != password: error = 'ユーザ名かパスワードが間違っています' raise HTTPException( status_code=HTTP_401_UNAUTHORIZED, detail=error, headers={"WWW-Authenticate": "Basic"}, ) # 特に問題がなければ管理者ページへ return templates.TemplateResponse('admin.html', {'request': request, 'user': user, 'task': task}) |
各処理の詳細はコメントで書いたので、読んでいただければ理解できるかと思います。
SQLAlchemyでは、 filter() でSQL構文でいう「WHERE」を扱うことができます。
また、 first() で該当クエリを上から1つ、 all() ですべてをリスト化して取得できます。
いずれもなければ、 None が返ってきます。
確認
では、動作確認をしてみましょう!

Basic認証画面
うまく動作していそうです!
わざと間違えると、簡単なエラーが返ってくることも確認できました。
ユーザをWeb上で登録できるようにする
現状、ユーザを登録するには、Pythonコードから一つ一つデータベースにコミットしなければなりません。
このままでは、誰もが使えるアプリケーションではないので、Web上からユーザを新たに登録できるようにします。
ルーティング
そうと決まれば新たに「register」という名前で『URL』『コントローラ』『ビュー』を作りましょう。
1 2 3 4 5 | # urls.py # FastAPIのルーティング用関数 app.add_api_route('/', index) app.add_api_route('/admin', admin) app.add_api_route('/register', register, methods=['GET', 'POST']) # new |
今回は、「GETメソッド」と「POSTメソッド」で処理を分けるので、上記のように引数で指定してあげます。
とりあえず、コントローラの処理は後回しにします。
1 2 3 4 5 6 7 | # controllers.py def register(request: Request): if request.method == 'GET': pass if request.method == 'POST': pass |
ビューも「templates/resiter.html」というファイルを作っておきましょう。
コントローラのGETメソッド処理を書く
つぎに、GETメソッドで受け取ったときの処理を書きます。
しかし、ここでは「単にビューを返すだけ」なので特に面白いことはありません…。
1 2 3 4 5 | if request.method is 'GET': return templates.TemplateResponse('register.html', {'request': request, 'username': '', 'error': []}) |
これだけです。
ビューを作る
次は、「ビュー」を作りましょう。
必要なのは、データベースに登録するための情報だけなので、それに見合ったフォームを書けばよいだけです!
今回は、「利用規約に同意するチェックボックス」や、「メール認証」などは今回は省きます。
(そんなに難しい実装でもないので、余力のある人は是非実装してみてください)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | {% extends "layout.html" %} {% block content %} <br> <div class="row"> <div class="col-md-10"> <h1>User Registration</h1> </div> <div class="col-md-2"> <a href="/admin" class="btn btn-primary">ログイン</a> </div> </div> <hr> <form action="/register" method="post"> <br> <p> ユーザ名<br> <input type="text" size="30" maxlength="20" name="username" value="{{username}}" placeholder="4文字以上20文字以下の半角英数字"> </p> <br> <p> パスワード<br> <input type="password" size="30" maxlength="20" name="password" placeholder="6文字以上20文字以下の半角英数字"> </p> <br> <p> パスワード (確認用)<br> <input type="password" size="30" maxlength="20" name="password_tmp" placeholder="6文字以上20文字以下の半角英数字"> </p> <br> <p> メールアドレス<br> <input type="email" size="40" maxlength="60" name="mail" placeholder="sample@example.com"> </p> <br><br> <button type="submit" class="btn btn-primary">登録</button> </form> <p class="text-danger">{% for e in error %}{{ e }}<br>{% endfor %}</p> {% endblock %} |
後々のことを考えて、エラー表示する部分も実装しています。

登録フォームのビュー
コントローラのPOSTメソッドを書く前に...
ここからが本題です。
先ほど作ったフォームから、POSTとしてデータが渡されました。
これらのデータは、非同期処理として処理されるため、コントローラの関数の先頭に async をつけます。
そして、フォームデータの受け取り方は以下のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 | async def register(request: Request): if request.method == 'GET': return templates.TemplateResponse('register.html', {'request': request}) if request.method == 'POST': # POSTデータ data = await request.form() username = data.get('username') password = data.get('password') password_tmp = data.get('password_tmp') mail = data.get('mail') |
このように、starletteのRequestクラスの form() 関数を使うことで実現できます。
今回は、GETとPOSTの処理を同じURLで兼任しましたが、もしURL(コントローラ)を分ける場合、FastAPIでサポートされている Form() という関数を利用しても実現可能なようです。
そちらが気になる方は、以下の公式ドキュメントを参照してみてください。
【Form Data - FastAPI】
http://fastapi.tiangolo.com/tutorial/request-forms/
POSTメソッド処理を書く
下準備はできたので、実装していきましょう!
まずは、入力されたデータが「正しい」か「否」かを判定します。
そのために、正規表現を使ってみましょう。
新しくcontrollers.pyに、以下の「re」というモジュールをインポートし、各パターンを作ります。
コードを見た方が早いですね。
1 2 3 4 5 | # controllers.py import re # new pattern = re.compile(r'\w{4,20}') # 任意の4~20の英数字を示す正規表現 pattern_pw = re.compile(r'\w{6,20}') # 任意の6~20の英数字を示す正規表現 pattern_mail = re.compile(r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$') # e-mailの正規表現 |
文字列の先頭に r がついているのは、バックスラッシュなどをエスケープしなくても良いようにするためです。
そうしたら、POSTメソッドの処理を書きましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | async def register(request: Request): if request.method == 'GET': return templates.TemplateResponse('register.html', {'request': request, 'username': '', 'error': []}) if request.method == 'POST': # POSTデータ data = await request.form() username = data.get('username') password = data.get('password') password_tmp = data.get('password_tmp') mail = data.get('mail') # new ここから error = [] tmp_user = db.session.query(User).filter(User.username == username).first() # 怒涛のエラー処理 if tmp_user is not None: error.append('同じユーザ名のユーザが存在します。') if password != password_tmp: error.append('入力したパスワードが一致しません。') if pattern.match(username) is None: error.append('ユーザ名は4~20文字の半角英数字にしてください。') if pattern_pw.match(password) is None: error.append('パスワードは6~20文字の半角英数字にしてください。') if pattern_mail.match(mail) is None: error.append('正しくメールアドレスを入力してください。') # エラーがあれば登録ページへ戻す if error: return templates.TemplateResponse('register.html', {'request': request, 'username': username, 'error': error}) # 問題がなければユーザ登録 user = User(username, password, mail) db.session.add(user) db.session.commit() db.session.close() return templates.TemplateResponse('complete.html', {'request': request, 'username': username}) |
コードは大したことをやっていないので、読んでいただくと処理が理解できるかと思います!
登録完了のビューをつくる
最後に、「登録完了の画面」を作成しましょう。
これも、そんなに凝ったものでなくて大丈夫ですが、そのままスムーズにログインに移行できるものが良いですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | {% extends "layout.html" %} {% block content %} <br> <div class="row"> <div class="col-md-10"> <h1>Complete your registration!</h1> </div> <div class="col-md-2"> <a href="/" class="btn btn-primary">トップページへ</a> </div> </div> <hr> <p>{{ username }}さん。toDo アプリケーションへようこそ。</p> <p>早速ログインしてみましょう!</p> <br> <p><a href="/admin" class="btn btn-primary btn-lg">ログイン</a></p> {% endblock %} |
確認

適当にユーザ登録してみる

登録完了
第3回:さいごに
ここで第3回は、終わりです。
今回は、「認証」と「ユーザ登録」について実装をしました。
FastAPIの場合での、シンプルなベーシック認証についてご理解いただけたかと思います。
次回は、管理者ページ(ユーザページ)をもっとToDoアプリケーションらしくしていきます!
第4回の記事はこちら
ResponderとFastAPIを実際に使って比較してみた
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン