FlutterIntermédiaire / Avancé5 min de lecturePublié le 12 août 2026

Architecture Dual Flow Google OAuth 2.0 : Flutter & Laravel

Mettre en place une authentification Google robuste combinant sessions web et jetons Bearer Sanctum pour Flutter.

Waffo Lele Rostand
Waffo Lele Rostand
Architecte Full Stack, Graphiste & Consultant
#Flutter #Laravel #OAuth 2.0 #Sanctum #Fortify #Dart #PHP
Technologies : Flutter (Dart), Laravel (PHP), Laravel Sanctum, Laravel Socialite, Google OAuth 2.0
Objectif : Unifier l'authentification Google entre une application web (cookies de session) et une application mobile Flutter (jeton API stateless).

Le Défi Architectural : Web vs Mobile #

Dans une application hybride moderne, l'authentification présente une divergence fondamentale :

  • Sur le Web : L'authentification repose sur des cookies de session HTTP-only (gérés nativement par Laravel).

  • Sur Mobile (Flutter) : L'application communique via des APIs REST stateless et nécessite un jeton d'accès sécurisé (Personal Access Token Sanctum) envoyé dans le header Authorization: Bearer .


Pour éviter de gérer deux systèmes parallèles ou de multiplier les flux d'authentification, nous utilisons le pattern Dual Flow : la WebView mobile délègue l'échange OAuth à l'API Laravel, qui crée ou associe l'utilisateur puis renvoie le jeton via un Deep Link.

1. Configuration Google Cloud & Schéma DB #

Identifiant OAuth 2.0 #

Dans Google Cloud Console, créez un identifiant de type Application Web avec les URIs de redirection :

  • Web : https://mondomaine.com/auth/google/callback

  • Mobile : https://api.mondomaine.com/api/auth/google/mobile/callback


Remarque importante : Réutiliser le Client ID Web pour le flux mobile WebView dispense de configurer les empreintes SHA-1 Android Keystore côté Google Cloud.

Migration Base de Données #

php
Schema::table('users', function (Blueprint $table) {
    $table->string('google_id')->nullable()->index();
    $table->text('google_token')->nullable();
    $table->boolean('has_custom_password')->default(true);
});

2. Implémentation Backend Laravel #

Le contrôleur expose deux endpoints : l'un génère l'URL d'autorisation Google, l'autre réceptionne le retour, réconcilie l'utilisateur et émet le jeton Sanctum.

php
namespace App\Http\Controllers\Api;

use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;

class MobileGoogleController extends Controller
{
    class=class="text-emerald-400">"text-slate-500 italic">// 1. Génère l'URL de connexion Google
    public function redirect(): JsonResponse
    {
        $url = Socialite::driver('google')
            ->redirectUrl(secure_url('/api/auth/google/mobile/callback'))
            ->stateless()
            ->with(['prompt' => 'select_account'])
            ->redirect()->getTargetUrl();

        return response()->json(['success' => true, 'url' => $url]);
    }

    class=class="text-emerald-400">"text-slate-500 italic">// 2. Callback : réconciliation DB + émission du jeton Sanctum
    public function callback(): RedirectResponse
    {
        $googleUser = Socialite::driver('google')
            ->redirectUrl(secure_url('/api/auth/google/mobile/callback'))
            ->stateless()->user();

        class=class="text-emerald-400">"text-slate-500 italic">// Réconciliation : google_id -> email existant -> création
        $user = User::where('google_id', $googleUser->getId())->first()
            ?? User::where('email', $googleUser->getEmail())->first()
            ?? User::create([
                'name'              => $googleUser->getName() ?? 'Utilisateur Google',
                'email'             => $googleUser->getEmail(),
                'password'          => Hash::make(Str::random(32)),
                'google_id'         => $googleUser->getId(),
                'email_verified_at' => now(),
            ]);

        $token = $user->createToken('mobile-app')->plainTextToken;

        class=class="text-emerald-400">"text-slate-500 italic">// Redirection vers le Deep Link de l'app mobile
        return redirect()->to("mondomaine:class=class="text-emerald-400">"text-slate-500 italic">//auth/callback?token={$token}");
    }
}

3. Configuration Mobile & Flutter #

xml
<intent-filter android:autoVerify="false">
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="mondomaine" android:host="auth"/>
</intent-filter>

Interception dans la WebView Flutter #

Dans l'écran de connexion Flutter, ouvrez l'URL Google via flutter_inappwebview et interceptez le callback contenant le jeton :

dart
bool _checkCallbackUrl(String url) {
  if (url.contains('token=')) {
    final token = Uri.parse(url).queryParameters['token'];
    if (token != null) {
      _saveTokenAndContinue(token);
      return true;
    }
  }
  return false;
}

void _saveTokenAndContinue(String token) async {
  await secureStorage.write(key: 'auth_token', value: token);
  await userNotifier.loadProfile(); class=class="text-emerald-400">"text-slate-500 italic">// GET /api/user avec Bearer token
  if (mounted) Navigator.pop(context, true);
}

Les 3 Pièges Majeurs à Éviter #

1. Erreur redirect_uri_mismatch : Derrière un reverse-proxy (Nginx, Traefik, FrankenPHP), forcez toujours secure_url() pour garantir que le protocole généré est bien en https://.
2. Interception prématurée : Filtrez strictement la présence de token= dans l'URL pour ne pas fermer la WebView avant la validation finale.
3. Mots de passe fantômes : Si votre application gère un cache local du mot de passe pour la biométrie, videz-le lors d'une connexion Google pour ne pas induire l'utilisateur en erreur.

En Résumé #

Ce pattern Dual Flow isole la complexité OAuth sur le backend Laravel tout en fournissant à l'application Flutter un jeton Sanctum standard. Vous obtenez ainsi une authentification robuste, rapide et uniforme sur l'ensemble de votre écosystème.

Cet article vous a aidé ? N’hésitez pas à le partager ou à me contacter.
Échanger sur WhatsApp