🔐

Flutter REST API Login

2023/08/26に公開

モックを使ってログインをしてみた。

普段は、Firebase AuthとSupabase Authしか使ったことないので、JWT認証とかやってみようと思ったのですが、うまくいかなかったので、モックのAPI作って、HTTPリクエストを送って、ステータスコードが200だったら、画面遷移するロジックを作りました。

バックエンドのAPIを作る

今回は、使い慣れているFlaskでダミーのデータが入ったREST APIを作りました。
name hoge, password 1234という値があるのですが、これとPOSTした値が一致してたら、Flutterが画面遷移するようになっています。

本当はもっと複雑な仕組みなんでしょうね。だって、セッションとか Cookieとかあるんでね。

HTTPが成功するとこんなログが出る

このコードは、仮想環境でFlaskをインストールして作成しました。

from flask import Flask, request, jsonify

app = Flask(__name__)

DUMMY_ACCOUNT = {
    "name": "hoge",
    "password": "1234"
}


# hellというURLにアクセスしたら、helloのJSON返す
@app.route('/hello')
def hello():
    return jsonify({"message": "Hello World!"})


@app.route('/login', methods=['POST'])
def login():
    data = request.json
    if not data:
        return jsonify({"message": "Invalid data"}), 400

    name = data.get("name")
    password = data.get("password")

    if name == DUMMY_ACCOUNT["name"] and password == DUMMY_ACCOUNT["password"]:
        return jsonify({"message": "認証に成功"}), 200
    else:
        return jsonify({"message": "Invalid credentials"}), 401


if __name__ == "__main__":
    app.run(debug=True)

Flutterのコード

HTTP POSTでリクエストを送ると、サーバー側にリクエストが送られて成功したら画面遷移して、HomePageへ移動します。

status code 200だったら、画面遷移するコード。

main.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LoginPage(),
    );
  }
}

class LoginPage extends StatefulWidget {
  
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _nameController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _isLoading = false;

  Future<bool> login(String name, String password) async {
    try {
      // タイムアウトを設定(例:5秒)
      final response = await http.post(
        Uri.parse('http://127.0.0.1:5000/login'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({"name": name, "password": password}),
      ).timeout(Duration(seconds: 5));

      // status code 200なら、ログイン成功
      return response.statusCode == 200;
    } catch (e) {
      // タイムアウトや他のエラーが発生した場合
      return false;
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _nameController,
              decoration: const InputDecoration(hintText: "Name"),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _passwordController,
              decoration: const InputDecoration(hintText: "Password"),
              obscureText: true,
            ),
            const SizedBox(height: 16),
            _isLoading
                ? const CircularProgressIndicator()
                : ElevatedButton(
                    onPressed: () async {
                      setState(() {
                        _isLoading = true; // ローディング表示開始
                      });
                      final success = await login(
                          _nameController.text, _passwordController.text);
                      setState(() {
                        _isLoading = false; // ローディング表示終了
                      });
                      if (success) {
                        if (mounted) {
                          Navigator.pushReplacement(
                            context,
                            MaterialPageRoute(builder: (context) => HomePage()),
                          );
                        }
                      } else {
                        if (mounted) {
                          // ログイン失敗のメッセージ表示
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(
                              content: Text('ユーザー情報を入力してください'),
                            ),
                          );
                        }
                      }
                    },
                    child: const Text("Login"),
                  )
          ],
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          // ログアウトボタン
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () => Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => LoginPage())),
          ),
        ],
        title: Text("Home")),
      body: Center(
        child: Text("Welcome!"),
      ),
    );
  }
}

ログインページ
入力をする。

ログイン後のページ
このページが表示されたら成功

まとめ

今回は、ダミーでログインを再現するのをやってみました。
本当は、Webの言語をやってた時みたいにJWT認証でやりたかったのですが、難しくて再現できなかったです🙇‍♂️
もっとOutputして、外部サービスを使用しないで、認証機能を作れるようになりたいです。

Discussion