【Python】Flaskでミニマムな認証機構を作成する

## はじめに
> ※この記事は、私が運営している技術ブログ（[hirahira.blog](https://hirahira.blog/)）で公開していた内容を移植・加筆したものです。

PythonとFlaskで、ミニマムな認証機能を実装する方法を紹介します。

ライブラリ等を使わずにログイン機構を作成することは基本的に無いと思いますが、仕組みを理解するための学習用途であればおすすめです。

## 環境
- Windows 11
- Python 3.13.2
- Flask 3.1.0

## フォルダ構成
├ app.py
└ templates/
　├ login.html
　├ welcome.html
　└ contents.html

 ## コード内容の解説
 ### app.py
メインとなる認証機構を実装しています。

あくまで学習用途なので、セッションキー・ユーザー名・パスワードはソースコードにベタ書きです。

```python:app.py
from flask import Flask, request, redirect, render_template, flash, session

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'  # セッションの暗号化に使用するキー
app.config['USERNAME'] = 'user'  # ログイン用のユーザ名
app.config['PASSWORD'] = 'pass'  # ログイン用のパスワード

@app.route('/')
def index():
    # ログイン済みの場合はウェルカムページを表示、それ以外はログインページへリダイレクト
    if session.get('flag'):
        return render_template('welcome.html', username=session['username'])
    return redirect('/login')

@app.route('/login', methods=['GET'])
def login():
    # ログイン済みの場合はウェルカムページへリダイレクト
    if session.get('flag'):
        return redirect('/welcome')
    return render_template('login.html')  # ログインページを表示

@app.route('/login', methods=['POST'])
def login_post():
    # フォームから送信されたユーザ名とパスワードを取得
    username = request.form['username']
    password = request.form['password']
    # ユーザ名またはパスワードが一致しない場合はエラーメッセージを表示
    if username != app.config['USERNAME']:
        flash('ユーザ名が異なります')
    elif password != app.config['PASSWORD']:
        flash('パスワードが異なります')
    else:
        # 認証成功時にセッション情報を設定し、ウェルカムページへリダイレクト
        session['flag'] = True
        session['username'] = username
        return redirect('/welcome')
    return redirect('/login')  # 認証失敗時はログインページへリダイレクト

@app.route('/welcome')
def welcome():
    # ログイン済みの場合はウェルカムページを表示、それ以外はログインページへリダイレクト
    if session.get('flag'):
        return render_template('welcome.html', username=session['username'])
    return redirect('/login')

@app.route('/contents')
def contents():
    # ログイン済みの場合はコンテンツページを表示、それ以外はログインページへリダイレクト
    if session.get('flag'):
        return render_template('contents.html', username=session['username'])
    return redirect('/login')

@app.route('/logout')
def logout():
    # セッション情報を削除し、ログインページへリダイレクト
    session.pop('username', None)
    session.pop('flag', None)
    flash('ログアウトしました')
    return redirect('/login')

if __name__ == '__main__':
    # デバッグモードでアプリケーションを実行
    app.run(debug=True)
```

### login.html
ログイン画面です。ユーザー名やパスワードが異なる場合はメッセージが表示されます。
```html:login.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ログイン</title>
  </head>
  <body>
    {% with messages = get_flashed_messages() %}
      {% if messages %}
        {% for message in messages %}
          <p>{{ message }}</p>
        {% endfor %}
      {% endif %}
    {% endwith %}
      <h1>ようこそ！</h1>
      <form action="/login" method="post">
      <div>
        <label for="user">ユーザー名</label>
        <input type="text" id="user" name="username">
      </div>
      <div>
        <label for="pass">パスワード</label>
        <input type="password" id="pass" name="password">
      </div>
      <button type="submit">ログイン</button>
    </form>
  </body>
</html>
```

### welcome.html
ログイン後に表示されるページです。
ログインしたときのユーザーIDを`render_template`で渡しています。

ログアウトをクリックするとログイン画面に戻ります。
```html:welcome.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Welcome</title>
</head>
<body>
  <h1>Welcomeページ</h1>
  <h2>ようこそ！ {{ username }}さん</h2>
  <p>このページはログインしないと表示されません</p>
  <ul>
    <li><a href="{{ url_for('contents') }}">コンテンツ</a></li>
    <li><a href="{{ url_for('logout') }}">ログアウト</a></li>
  </ul>
</body>
</html>
```

### contents.html
ログイン後に表示されるページから遷移できるページです。
```html:contents.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>コンテンツ</title>
</head>
<body>
  <h1>コンテンツページ</h1>
  <h2>こんにちは！ {{ username }}さん</h2>
  <p>このページはログインしないと表示されません</p>
  <ul>
    <li><a href="{{ url_for('welcome') }}">Welcomeページへ</a></li>
    <li><a href="{{ url_for('logout') }}">ログアウト</a></li>
  </ul>
</body>
</html>
```

## 動かしてみる
`python app.py` を実行し、http://127.0.0.1:5000 にアクセスするとログイン画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586427/a4a0691a-99b9-4310-b346-baa7cf36bd64.png)

設定したユーザー名とパスワードを入力するとWelcomeが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586427/c06bb64e-63e1-40da-8945-f9deafb77c1f.png)

セッションでフラグとユーザー名を変数で保持しており、ログアウトするかブラウザを閉じるまではログイン不要でページを閲覧できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586427/9c7e4901-acd3-4a95-bbc6-8b177cbb8f24.png)


ログアウトしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586427/2cb08e90-2c98-47b3-8b37-7612df4865ba.png)

## おわりに
Flaskを使って簡単な認証機構を作成しました。

本格的なアプリを作る際は、認証ライブラリの導入を検討しましょう。