【第3回】FastAPIチュートリアル: toDoアプリを作ってみよう【認証・ユーザ登録編】
IT技術
FastAPIチュートリアル: toDoアプリを作ってみよう~第3回~
前回の記事「【第2回】FastAPIチュートリアル: ToDoアプリを作ってみよう【モデル構築編】」の続きです。
今回も、ToDoアプリを作る過程で、FastAPIの使い方を学んでいきましょう!
第1回はこちら
認証
FastAPIでは、HTTP BasicAuth(いわゆるベーシック認証)とOAuth2(オーオース)と呼ばれる認証をサポートしています。
本アプリケーションは、あまり凝ったアプリケーションでもないので、ベーシック認証を利用することにします。
今の時代、「ログインフォームなどを作成してログイン」する形のほうが一般的ではありますが、今回はご容赦ください。
さて、FastAPIでベーシック認証を使用するにあたり、下準備がいくつかあります。
新たにimportするモジュール
controller.py に、以下のモジュールを新しくインポートしてください。
(重複しているものは、適宜無視してください)
1from fastapi import FastAPI, Depends, HTTPException # new
2from fastapi.security import HTTPBasic, HTTPBasicCredentials # new
3
4from starlette.templating import Jinja2Templates
5from starlette.requests import Request
6from starlette.status import HTTP_401_UNAUTHORIZED # new
7
8import db # new
9from models import User, Task # new
10
11import hashlib # new
上から2つは、FastAPIでベーシック認証を使うのに必要なクラスです。
後半の新しく追加したものは、入力された「ユーザ名」や「パスワード」がデータベースにあるものと一致するかを検証するのに使います。
管理者ページのコントローラを修正する
次に、管理者ページのビューを表示する前のコントローラadmin() を修正していきます。
というより、ほとんど変更しますので、上からコピペでも構いません。
1def admin(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
2 # Basic認証で受け取った情報
3 username = credentials.username
4 password = hashlib.md5(credentials.password.encode()).hexdigest()
5
6 # データベースからユーザ名が一致するデータを取得
7 user = db.session.query(User).filter(User.username == username).first()
8 task = db.session.query(Task).filter(Task.user_id == user.id).all() if user is not None else []
9 db.session.close()
10
11 # 該当ユーザがいない場合
12 if user is None or user.password != password:
13 error = 'ユーザ名かパスワードが間違っています'
14 raise HTTPException(
15 status_code=HTTP_401_UNAUTHORIZED,
16 detail=error,
17 headers={"WWW-Authenticate": "Basic"},
18 )
19
20 # 特に問題がなければ管理者ページへ
21 return templates.TemplateResponse('admin.html',
22 {'request': request,
23 'user': user,
24 'task': task})
各処理の詳細はコメントで書いたので、読んでいただければ理解できるかと思います。
SQLAlchemyでは、filter() でSQL構文でいう「WHERE」を扱うことができます。
また、first() で該当クエリを上から1つ、all() ですべてをリスト化して取得できます。
いずれもなければ、None が返ってきます。
確認
では、動作確認をしてみましょう!
うまく動作していそうです!
わざと間違えると、簡単なエラーが返ってくることも確認できました。
ユーザをWeb上で登録できるようにする
現状、ユーザを登録するには、Pythonコードから一つ一つデータベースにコミットしなければなりません。
このままでは、誰もが使えるアプリケーションではないので、Web上からユーザを新たに登録できるようにします。
ルーティング
そうと決まれば新たに「register」という名前で『URL』『コントローラ』『ビュー』を作りましょう。
1# urls.py
2# FastAPIのルーティング用関数
3app.add_api_route('/', index)
4app.add_api_route('/admin', admin)
5app.add_api_route('/register', register, methods=['GET', 'POST']) # new
今回は、「GETメソッド」と「POSTメソッド」で処理を分けるので、上記のように引数で指定してあげます。
とりあえず、コントローラの処理は後回しにします。
1# controllers.py
2def register(request: Request):
3 if request.method == 'GET':
4 pass
5
6 if request.method == 'POST':
7 pass
ビューも「templates/resiter.html」というファイルを作っておきましょう。
コントローラのGETメソッド処理を書く
つぎに、GETメソッドで受け取ったときの処理を書きます。
しかし、ここでは「単にビューを返すだけ」なので特に面白いことはありません…。
1if request.method is 'GET':
2 return templates.TemplateResponse('register.html',
3 {'request': request,
4 'username': '',
5 'error': []})
これだけです。
ビューを作る
次は、「ビュー」を作りましょう。
必要なのは、データベースに登録するための情報だけなので、それに見合ったフォームを書けばよいだけです!
今回は、「利用規約に同意するチェックボックス」や、「メール認証」などは今回は省きます。
(そんなに難しい実装でもないので、余力のある人は是非実装してみてください)
1{% extends "layout.html" %}
2{% block content %}
3<br>
4<div class="row">
5 <div class="col-md-10">
6 <h1>User Registration</h1>
7 </div>
8 <div class="col-md-2">
9 <a href="/admin" class="btn btn-primary">ログイン</a>
10 </div>
11</div>
12
13<hr>
14<form action="/register" method="post">
15 <br>
16 <p>
17 ユーザ名<br>
18 <input type="text" size="30" maxlength="20" name="username" value="{{username}}" placeholder="4文字以上20文字以下の半角英数字">
19 </p>
20 <br>
21 <p>
22 パスワード<br>
23 <input type="password" size="30" maxlength="20" name="password" placeholder="6文字以上20文字以下の半角英数字">
24 </p>
25 <br>
26 <p>
27 パスワード (確認用)<br>
28 <input type="password" size="30" maxlength="20" name="password_tmp" placeholder="6文字以上20文字以下の半角英数字">
29 </p>
30 <br>
31 <p>
32 メールアドレス<br>
33 <input type="email" size="40" maxlength="60" name="mail" placeholder="sample@example.com">
34 </p>
35 <br><br>
36 <button type="submit" class="btn btn-primary">登録</button>
37</form>
38
39<p class="text-danger">{% for e in error %}{{ e }}<br>{% endfor %}</p>
40
41{% endblock %}
後々のことを考えて、エラー表示する部分も実装しています。
コントローラのPOSTメソッドを書く前に...
ここからが本題です。
先ほど作ったフォームから、POSTとしてデータが渡されました。
これらのデータは、非同期処理として処理されるため、コントローラの関数の先頭にasync をつけます。
そして、フォームデータの受け取り方は以下のように行います。
1async def register(request: Request):
2 if request.method == 'GET':
3 return templates.TemplateResponse('register.html',
4 {'request': request})
5
6 if request.method == 'POST':
7 # POSTデータ
8 data = await request.form()
9 username = data.get('username')
10 password = data.get('password')
11 password_tmp = data.get('password_tmp')
12 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# controllers.py
2import re # new
3pattern = re.compile(r'\w{4,20}') # 任意の4~20の英数字を示す正規表現
4pattern_pw = re.compile(r'\w{6,20}') # 任意の6~20の英数字を示す正規表現
5pattern_mail = re.compile(r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$') # e-mailの正規表現
文字列の先頭にr がついているのは、バックスラッシュなどをエスケープしなくても良いようにするためです。
そうしたら、POSTメソッドの処理を書きましょう!
1async def register(request: Request):
2 if request.method == 'GET':
3 return templates.TemplateResponse('register.html',
4 {'request': request,
5 'username': '',
6 'error': []})
7
8 if request.method == 'POST':
9 # POSTデータ
10 data = await request.form()
11 username = data.get('username')
12 password = data.get('password')
13 password_tmp = data.get('password_tmp')
14 mail = data.get('mail')
15
16 # new ここから
17 error = []
18
19 tmp_user = db.session.query(User).filter(User.username == username).first()
20
21 # 怒涛のエラー処理
22 if tmp_user is not None:
23 error.append('同じユーザ名のユーザが存在します。')
24 if password != password_tmp:
25 error.append('入力したパスワードが一致しません。')
26 if pattern.match(username) is None:
27 error.append('ユーザ名は4~20文字の半角英数字にしてください。')
28 if pattern_pw.match(password) is None:
29 error.append('パスワードは6~20文字の半角英数字にしてください。')
30 if pattern_mail.match(mail) is None:
31 error.append('正しくメールアドレスを入力してください。')
32
33 # エラーがあれば登録ページへ戻す
34 if error:
35 return templates.TemplateResponse('register.html',
36 {'request': request,
37 'username': username,
38 'error': error})
39
40 # 問題がなければユーザ登録
41 user = User(username, password, mail)
42 db.session.add(user)
43 db.session.commit()
44 db.session.close()
45
46 return templates.TemplateResponse('complete.html',
47 {'request': request,
48 'username': username})
コードは大したことをやっていないので、読んでいただくと処理が理解できるかと思います!
登録完了のビューをつくる
最後に、「登録完了の画面」を作成しましょう。
これも、そんなに凝ったものでなくて大丈夫ですが、そのままスムーズにログインに移行できるものが良いですね。
1{% extends "layout.html" %}
2{% block content %}
3<br>
4<div class="row">
5 <div class="col-md-10">
6 <h1>Complete your registration!</h1>
7 </div>
8 <div class="col-md-2">
9 <a href="/" class="btn btn-primary">トップページへ</a>
10 </div>
11</div>
12
13<hr>
14<p>{{ username }}さん。toDo アプリケーションへようこそ。</p>
15<p>早速ログインしてみましょう!</p>
16<br>
17<p><a href="/admin" class="btn btn-primary btn-lg">ログイン</a></p>
18
19
20{% endblock %}
確認
第3回:さいごに
ここで第3回は、終わりです。
今回は、「認証」と「ユーザ登録」について実装をしました。
FastAPIの場合での、シンプルなベーシック認証についてご理解いただけたかと思います。
次回は、管理者ページ(ユーザページ)をもっとToDoアプリケーションらしくしていきます!
第4回の記事はこちら
ResponderとFastAPIを実際に使って比較してみた
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit
おすすめ記事
immichを知ってほしい
2024.10.31