初めてFlutterを触ってみた- Flutter × Laravel 認証- ③
初めてFlutterを触ってみた③
過去の記事の続きになります。
この記事では、Laravelとの連携方法に焦点を当て、FlutterアプリからAPIを介してトークンベースの認証を実装する方法を解説します。
Laravel Sanctumについて
APIトークンとは
Laravel Sanctumは、シンプルなトークンベースの認証システムを提供します。トークンを用いることで、クライアント(Flutterなど)がサーバー(Laravel)と安全にやり取りすることができます。
トークンは、ユーザーがログインするときに生成され、各リクエストでそのトークンを使ってユーザーを認証します。これにより、ユーザーがすでにログインしているかのように動作することができます。APIトークン認証は、SPA(シングルページアプリケーション)やモバイルアプリとの連携に適しています。
ルーティングの設定
api.phpディレクトリを作成して、APIルートを定義していきます。
$ php artisan install:api
// 認証なしルート
Route::post('/register', [RegisterController::class, 'store']);
Route::post('/login', [AuthController::class, 'store']);
// 認証済みルート
Route::group(['middleware' => 'auth:sanctum'], function () {
Route::post('/logout', [AuthController::class, 'destroy']);
Route::get('/user', function (Request $request) {
return $request->user();
});
});
- 認証なしルート: これらのエンドポイントは、認証されていないユーザーでもアクセスできます。たとえば、新規ユーザー登録やログイン用のエンドポイントです。
- 認証済みルート auth:sanctumミドルウェアを適用することで、トークン認証を行います。このルートには、ログイン後のユーザーのみがアクセスできます。認証に失敗した場合、401 Unauthorized エラーが返されます。
トークンの作成と削除
Sanctumを使ってトークンの作成や削除を行う際の具体例を示します。
トークンの作成
ユーザーがログインした際にトークンを発行します。
$token = $user->createToken('MyAppToken')->plainTextToken;
return response()->json(['token' => $token]);
トークンの削除
ユーザーがログアウトする際には、トークンを削除します。
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out successfully']);
上記より、今回は以下のようなAuthControllerを作成しました。
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Enums\Status;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Auth\LoginRequest;
use App\Models\User;
use App\Utils\TwoFactorAuth;
use Illuminate\Http\Request;
class AuthController extends Controller
{
public function store(LoginRequest $request)
{
$request->authenticate();
$user = User::where('email', $request->email)
->where('status', Status::ACTIVE)
->first();
if (!$user) {
return response()->json(['message' => '該当ユーザーが見つかりませんでした。'], 404);
}
$token = $request->user()->createToken('auth_token:user'. $user->id)->plainTextToken;
TwoFactorAuth::sendCode($user);
return response()->json(['token' => $token]);
}
public function destory(Request $request)
{
$request->user()->tokens()->delete();
return response()->json(null, 200);
}
}
storeメソッドの流れ
- 認証処理の実行
$request->authenticate()
メソッドで、メールアドレスとパスワードによるユーザー認証を行います。 - ユーザーのステータス確認
Userモデルを使用して、指定したメールアドレスとStatus::ACTIVEのステータスを持つユーザーが存在するかをチェックします。該当しない場合は、404エラーを返します。 - トークンの発行
ログインに成功した場合、ユーザーIDを含むユニークなトークンを生成します。plainTextTokenを使用して、クライアントにトークンを返します。 - 二段階認証コードの送信
TwoFactorAuth::sendCode()メソッドを使って、ユーザーに二段階認証コードを送信します。
destoryメソッドの流れ
- トークンの削除
tokens()->delete()メソッドで、ユーザーが持つすべてのトークンを削除します。これにより、ログアウト後は再認証が必要になります。
Flutter側のAPI接続について
Flutterを使用してLaravelのAPIと通信する際には、httpパッケージを使ってAPIリクエストを行います。Laravel Sanctumを使って発行されるトークンを利用し、認証付きリクエストを送る方法について説明します。また、トークンの管理にはshared_preferencesパッケージを使用します。
Laravelから受け取るトークンの役割
FlutterアプリとLaravelサーバー間で安全にデータをやり取りするため、Laravel側で発行されたトークンを使って各リクエストを認証します。以下がその役割です:
-
トークンの生成
ユーザーがログインした際に、サーバー側でトークンが発行され、クライアントに返されます。このトークンはFlutterアプリがリクエストを送信する際に使用し、ユーザーの認証を行います。 -
トークンの使用
クライアントは各リクエストにトークンを含めることで、サーバーに自分が認証済みのユーザーであることを証明します。サーバー側は受け取ったトークンを検証し、正しいトークンであればリクエストを許可します。 -
トークンの管理
トークンはクライアント側で安全に管理する必要があります。ログイン後に取得したトークンをローカルストレージ(例:SharedPreferences)に保存し、APIリクエスト時に利用します。ログアウト時にはトークンを削除して無効化します。
FlutterでのServiceUtilsについて
以下のServiceUtilsクラス
では、トークンの管理とAPIリクエストの共通処理をまとめています。
import 'package:flutter_sample/src/constants/app_const.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class ServiceUtils {
static final String baseApiUrl = AppConst.baseApiUrl;
// リクエストヘッダーを取得
static Map<String, String> getHeaders(String? token) {
final header = token != null
? {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
'Accept': 'application/json',
} : {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
return header;
}
// POSTリクエスト
static postRequest(String uri, String? token, String? data) async {
final url = Uri.parse('$baseApiUrl/$uri');
final headers = getHeaders(token);
final response = await http.post(
url,
headers: headers,
body: data,
);
return response;
}
...
// Tokenを取得
static Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
return token;
}
// Tokenを保存
static Future<void> setToken(String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token);
}
// Tokenを削除
static Future<void> removeToken() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('auth_token');
}
}
-
getHeadersメソッド
APIリクエストに必要なヘッダーを生成します。トークンが指定されている場合は、Authorizationヘッダーを追加して認証付きリクエストを行います。認証が不要なリクエストの場合、Content-TypeとAcceptのヘッダーのみが設定されます。 -
postRequestメソッド
サーバーに対してPOSTリクエストを送信します。uriにはAPIのエンドポイントを指定し、tokenには認証用トークンを渡します。dataにはリクエストのボディに含めるデータ(JSON形式)を指定します。HTTPレスポンスを返すので、レスポンスの内容をチェックして次の処理を行います。 -
getTokenメソッド
SharedPreferencesを使用してローカルストレージに保存されているトークンを取得します。ログイン済みのユーザーの認証情報を取得するために利用されます。 -
setTokenメソッド
ログイン時などにサーバーから受け取ったトークンをローカルストレージに保存します。これにより、後続のリクエストでトークンを使用して認証できます。 -
removeTokenメソッド
ログアウト時にトークンをローカルストレージから削除します。これにより、セキュリティを確保し、再度ログインしない限り認証ができない状態にします。
Flutterでのログインリクエスト
Flutterアプリでログインを行い、サーバーからトークンを受け取る方法を示します。ログインに成功すると、サーバーから受け取ったトークンをローカルに保存します。
import 'dart:convert';
import 'package:auth_demo/src/const.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
...
// ログインリクエスト
Future<void> login(String email, String password) async {
final requestData = jsonEncode({
'email': email,
'password': password,
});
final response = await ServiceUtils.postRequest('login', null, requestData);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final token = data['token'];
// トークンを保存
await ServiceUtils.setToken(token);
} else {
final message = jsonDecode(response.body)['message'] ?? 'ログインに失敗しました';
throw ApiException(message);
}
}
...
処理の流れ
- ログインデータの準備
emailとpasswordをJSON形式にエンコードし、requestDataとして準備します。このデータがログインリクエストのボディに含まれ、サーバーに送信されます。 - APIリクエストの送信
ServiceUtils.postRequestメソッド
を使用して、loginエンドポイントにPOSTリクエストを送信します。リクエストにはユーザーのログイン情報が含まれ、サーバー側で認証処理が行われます。 - レスポンスの処理
サーバーからのレスポンスを確認し、ステータスコードが200(成功)である場合は、レスポンスボディをデコードして取得します。これにより、サーバーが返したトークンを取得できます。 - トークンの保存
サーバーから受け取ったトークンをServiceUtils.setTokenメソッド
を使ってローカルストレージ(SharedPreferences)に保存します。これにより、以降の認証付きリクエストでトークンを利用できるようになります。 - エラーハンドリング
ステータスコードが200以外の場合は、レスポンスからエラーメッセージを取得し、ApiExceptionをスローしてログイン失敗を通知します。エラーメッセージがない場合は、“ログインに失敗しました”というデフォルトメッセージを表示します。
ログアウトリクエスト
Flutterアプリでログアウトを行う際の処理を示します。サーバーにログアウトリクエストを送信し、成功した場合にはローカルに保存されたトークンを削除します。
// ログアウトリクエスト
Future<void> logout() async {
final token = await ServiceUtils.getToken();
if (token != null) {
final response = await ServiceUtils.postRequest('logout', token, null);
if (response.statusCode != 200) {
final message = jsonDecode(response.body)['message'] ?? 'ログアウトに失敗しました';
throw ApiException(message);
}
// トークンを削除
await ServiceUtils.removeToken();
}
}
処理の流れ
- トークンの取得
ServierUtils.getTokenメソッド
を使用して、ローカルストレージに保存されているトークンを取得します。ログアウトリクエストには認証済みのトークンが必要であり、このトークンを使用してユーザーの認証状態をサーバー側で解除します。 - トークンの有無を確認
取得したトークンがnullでない場合にのみ、ログアウトリクエストを実行します。トークンがnullの場合、ログインしていない、もしくはすでにログアウト済みであるため、何も処理を行いません。 - ログアウトリクエストの送信
ServiceUtils.postRequestメソッド
を使ってlogoutエンドポイントにPOSTリクエストを送信します。このリクエストには、取得したトークンが含まれており、サーバーはトークンを使ってログアウト処理を実行します。 - レスポンスの確認
サーバーからのレスポンスを確認し、ステータスコードが200(成功)でない場合はエラーメッセージを取得して例外をスローします。エラーメッセージがない場合は、“ログアウトに失敗しました”というデフォルトメッセージを表示します。 - トークンの削除
サーバーからログアウトが成功したレスポンスが返ってきた場合、ローカルストレージに保存されているトークンをServiceUtils.removeTokenメソッド
で削除します。これにより、ローカルに認証情報が残らないようにします。
まとめ
今回は、Laravel Sanctumを使ったトークンベースの認証と、Flutterを利用したAPI接続の実装について解説しました。
次回は、Flutterの画面遷移や、これまで触れてこなかったFlutterライブラリについて、ゆるくお話しする予定です。
具体的なトピックはまだ決まっていませんが、気軽に読んでもらえたら嬉しいです!
Flutterに詳しい方や初心者の方からのアドバイスも大歓迎ですので、ぜひコメントいただけると嬉しいです!
Discussion