 メディア編集部
メディア編集部【第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を実際に使って比較してみた
こちらの記事もオススメ!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ

「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit















