Flask: Flask-JWT-Extended の使い方

こちらのサンプルを改造しました。
[asic Usage](https://flask-jwt-extended.readthedocs.io/en/stable/basic_usage/)

Ubuntu での必要なライブラリーのインストール

```bash
sudo apt install python3-python-flask-jwt-extended
```

JWT_SECRET_KEY は次のコマンドで作成しました。

```bash
openssl rand -hex 32
```

ユーザーは 3人にしました。

<table>
<tr><th>user</th><th>password</th></tr>
<tr><td>scott</td><td>tiger123</td></tr>
<tr><td>jack</td><td>jack123</td></tr>
<tr><td>betty</td><td>betty123</td></tr>
</table>

## サーバープログラムの仕様

<ul>
<li>http://localhost:5000/login</li>
 POST で ユーザー名とパスワードを渡すと、JWT トークンが返される

<li>http://localhost:5000/protected</li>
  GET で　ヘッダーに　正しい JWT トークンがあれば、logged_in_as というメッセージが返される
</ul>

## サーバープログラム

```py:app.py
from flask import Flask
from flask import jsonify
from flask import request

from flask_jwt_extended import create_access_token
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager

app = Flask(__name__)

# Setup the Flask-JWT-Extended extension
app.config["JWT_SECRET_KEY"] = "04e630f86a2d29c1fbbe76f5e8ea80475720a93d9c497a408255b7f3873ca385"
jwt = JWTManager(app)


# Create a route to authenticate your users and return JWTs. The
# create_access_token() function is used to actually generate the JWT.
@app.route("/login", methods=["POST"])
def login():
	users = {}
	users['scott'] = 'tiger123'
	users['jack'] = 'jack123'
	users['betty'] = 'betty123'
	username = request.json.get("username", None)
	password = request.json.get("password", None)
#	if username != "scott" or password != "tiger123":
	if (not username in users) or (password != users[username]):
		return jsonify({"msg": "Bad username or password"}), 401

	access_token = create_access_token(identity=username)
	return jsonify(access_token=access_token)


# Protect a route with jwt_required, which will kick out requests
# without a valid JWT present.
@app.route("/protected", methods=["GET"])
@jwt_required()
def protected():
	# Access the identity of the current user with get_jwt_identity
	current_user = get_jwt_identity()
	return jsonify(logged_in_as=current_user), 200


if __name__ == "__main__":
	app.run()
```

## サーバーの実行

```bash
python app.py
```

```text
$ python app.py 
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
 Press CTRL+C to quit
```

## テストスクリプト

```bash
http GET :5000/protected
```

```text
$ http GET :5000/protected
HTTP/1.1 401 UNAUTHORIZED
Connection: close
Content-Length: 39
Content-Type: application/json
Date: Sun, 21 May 2023 08:44:44 GMT
Server: Werkzeug/2.2.2 Python/3.11.2

{
    "msg": "Missing Authorization Header"
}
```

ログイン (token の取得）

```bash:login.sh
http POST :5000/login username=scott password=tiger123 > scott.json
http POST :5000/login username=jack password=jack123 > jack.json
http POST :5000/login username=betty password=betty123 > betty.json
```

ページへのアクセス

```bash:get.sh
JWT=`jq .access_token scott.json | sed 's/"//g'`
http GET :5000/protected Authorization:"Bearer $JWT"
#
JWT=`jq .access_token jack.json | sed 's/"//g'`
http GET :5000/protected Authorization:"Bearer $JWT"
#
JWT=`jq .access_token betty.json | sed 's/"//g'`
http GET :5000/protected Authorization:"Bearer $JWT"
```

実行結果

```text
$ ./get.sh 
HTTP/1.1 200 OK
Connection: close
Content-Length: 25
Content-Type: application/json
Date: Sun, 21 May 2023 08:46:56 GMT
Server: Werkzeug/2.2.2 Python/3.11.2

{
    "logged_in_as": "scott"
}


HTTP/1.1 200 OK
Connection: close
Content-Length: 24
Content-Type: application/json
Date: Sun, 21 May 2023 08:46:57 GMT
Server: Werkzeug/2.2.2 Python/3.11.2

{
    "logged_in_as": "jack"
}


HTTP/1.1 200 OK
Connection: close
Content-Length: 25
Content-Type: application/json
Date: Sun, 21 May 2023 08:46:57 GMT
Server: Werkzeug/2.2.2 Python/3.11.2

{
    "logged_in_as": "betty"
}
```

## 確認したバージョン

```text
$ flask --version
Python 3.12.7
Flask 3.0.3
Werkzeug 3.0.3
```