個人的備忘録：夏のハッカソンで実装した Flask のログイン機能を紐解きながらアウトプットしてみた

## はじめに

前回の記事では、Flask を使った認証機能の基本について紹介しました。  
今回は、その続きとして **実際にログイン処理を実装する方法** を詳しく解説します。  

Flask の `session` を活用し、**ログインフォームの作成から認証、セッション管理、ログアウトまでの流れ** を見ていきます。  

前回の記事の続きです。

https://qiita.com/free-honda/items/22d58fc7ec10b37d97ef

## 書こうと思ったきっかけ

Flask でログイン機能を実装する際、認証処理の流れを**「どう設計するべきか」「どのように実装するのか」** について迷うことが多いと感じました。

:::note warn
***特に、**セッション管理やパスワードの取り扱い（ハッシュ化など）** には注意が必要です。***
:::

この記事では、以下のポイントを押さえながら、**実際のコードとともに解説** していきます。  

- **ログインフォームの作成**  
- **ユーザー認証（データベースとの照合）**  
- **セッションを活用したログイン状態の管理**  
- **ログアウト処理の実装**  

## 実際に実装してみた（Flask）
受講中の IT スクールのハッカソンで作成したチャットアプリのリポジトリを共有します。***必要に応じて参考にしてみてください。***

https://github.com/RareTECH-Iteam/Iteam_0819/blob/main/ChatApp/app.py

当時はまだコードの書き方が洗練されておらず、改善の余地が多い部分もありますが、温かい目で見守っていただければ幸いです。

### 1. ログインフォームの表示

```py
@app.route('/login')
def login():
    return render_template('registration/login.html')
```

`GET /login` にアクセスすると、ログインページ（`registration/login.html`）が表示される。  

### 2. ログイン処理

```py
@app.route('/login', methods=['POST'])
def userLogin():
    email = request.form.get('email')
    password = request.form.get('password')

    if not email or not password:  # 空のフォームチェック
        flash('メールアドレスまたはパスワードが入力されていません')
    else:
        user = dbConnect.getUser(email)  # メールアドレスを元にユーザー情報を検索

        if user is None:
            flash('このユーザーは存在しません')
        else:
            if not check_password_hash(user["password"], password):  # ハッシュ化されたパスワードと照合
                flash('パスワードが間違っています！')
            else:
                session['uid'] = user["uid"]
                return redirect('/')
    
    return redirect('/login')
```

- **`POST` リクエストで `email` と `password` を受け取る。**  
- `dbConnect.getUser(email)` でデータベースからユーザー情報を取得。  
- 入力された `password` がデータベースの `user["password"]` と一致するか確認。  

**一致した場合:**  
- `session['uid'] = user["uid"]` でセッションにユーザーの `uid` を保存。（ここでログインが完了！）  
- `redirect('/')` でホーム画面にリダイレクト。  

**一致しない場合:**  
- `flash('パスワードが間違っています！')` でエラーを表示し、再度 `/login` にリダイレクト。  

### 3. ログイン状態の確認

```py
@app.route('/')
def home():
    uid = session.get("uid")
    if uid is None:
        return redirect('/login')
    else:
        channels = dbConnect.getChatAll()
        channels = channels[::-1]
        user = dbConnect.getUserByUid(uid)
    return render_template('home.html', channels=channels, uid=uid, user=user)
```

- `session.get("uid")` でログイン状態を確認。  
- **ログインしていない (`uid is None`) 場合** は `/login` にリダイレクト。  
- **ログインしている場合** は `uid` をもとにユーザー情報を取得して、ホーム画面を表示。  

### 4. ログインユーザー名の取得

```py
@app.route('/get_logged_in_user', methods=['GET'])
def get_logged_in_user():
    logged_in_user_id = session.get('uid')
    if not logged_in_user_id:
        return jsonify({"message": "ログインしていません"}), 401
    logged_in_username = dbConnect.getUsernameByUid(logged_in_user_id)
    if not logged_in_username:
        return jsonify({"message": "ユーザー情報の取得に失敗しました"}), 500
    return jsonify({"username": logged_in_username}), 200
```

- **API エンドポイント `/get_logged_in_user` でログイン中のユーザー名を取得。**  
- `session.get('uid')` でログイン中のユーザー ID を取得し、データベースから `username` を検索。  

### 5. ログアウト

```py
@app.route('/logout')
def logout():
    return render_template('registration/login.html')
```

- `/logout` にアクセスすると、ログイン画面（`registration/login.html`）にリダイレクトするが、  
  **セッションを削除する処理がないため、実際にはログアウトできていない。**  

**ログアウトを実装する場合：**  
1. `session.pop('uid', None)` を実行してセッションを削除。  
2. `redirect('/login')` でログイン画面にリダイレクト。  

```py
@app.route('/logout')
def logout():
    session.pop('uid', None)  # セッションからユーザーIDを削除
    return redirect('/login')
```

こうすることで、`session['uid']` が削除され、再度アクセスした際にログインが必要になる。  

## まとめ
お読みいただきありがとうございました。***今回の記事を通じて、過去に実装した内容を振り返る良い機会となりました。***

:::note warn
**今後も、以前書いたコードを見直しながら、どのような仕組みだったのかを整理し、体系的にアウトプットしていきたいと考えています。**
:::

本記事は個人の備忘録としてまとめたものですが、温かく見守っていただけると嬉しいです。