Authentification sociale simplifiée avec Laravel Socialite
Fait partie de la série Améliorer votre blog VitePress avec une API Laravel
Avec la structure du projet et les outils qualité en place, nous pouvons commencer à construire la première fonctionnalité de l'API destinée aux utilisateurs : l'authentification. Comme les commentaires doivent appartenir à de vrais utilisateurs, c'est la suite logique.
Le problème, c'est que l'authentification est difficile, vraiment difficile. Il faut gérer les mots de passe, la vérification des adresses e-mail, la réinitialisation des mots de passe, l'authentification à deux facteurs, et bien plus encore. Heureusement, les frameworks comme Laravel proposent des fonctionnalités d'authentification intégrées et des packages officiels comme Fortify pour simplifier le processus.
Même avec ces outils, nous devons encore construire et gérer de nombreuses parties nous-mêmes : l'inscription, la connexion, la réinitialisation des mots de passe, la gestion du profil et les cas particuliers associés. Nous avons également besoin d'un service mail pour envoyer les e-mails.
Tout cela est possible, mais demande du temps et des efforts. Pour un blog simple comme le nôtre, nous pouvons plutôt utiliser GitHub pour l'authentification sociale. L'implémentation est plus simple et les utilisateurs peuvent se connecter avec un compte qu'ils possèdent déjà.
Avant de plonger dans l'implémentation, prenons un moment pour comprendre ce qu'est l'authentification sociale et pourquoi elle est utile.
L'authentification sociale en quelques mots
Traditionnellement, lorsqu'un utilisateur veut créer un compte sur un site, il doit remplir un formulaire d'inscription avec son adresse e-mail et un mot de passe. Ce fonctionnement est efficace depuis des années, mais il a ses inconvénients. De notre côté, nous devons gérer toutes ces données, tandis que l'utilisateur doit retenir un mot de passe supplémentaire.
Avec l'authentification sociale, les utilisateurs peuvent se connecter avec leurs comptes existants sur des plateformes comme GitHub, Google, Apple ou Facebook. Ils n'ont donc pas besoin de créer un nouveau compte ni de retenir un autre mot de passe. Ils utilisent leurs identifiants existants pour se connecter rapidement et facilement, ce qui réduit les frictions et améliore l'expérience.
Pour les plus techniques d'entre vous, voici un schéma de fonctionnement :
Le flux est simple :
- L'utilisateur clique sur le bouton « Se connecter avec GitHub » de votre site.
- L'utilisateur est redirigé vers la page d'autorisation de GitHub, où il peut autoriser votre application à accéder à son compte GitHub.
- Si l'utilisateur accorde cette autorisation, GitHub le redirige vers votre site avec un code d'autorisation.
- Votre site échange ce code contre un jeton d'accès, qui peut être utilisé pour accéder au compte GitHub de l'utilisateur.
- Votre site peut maintenant utiliser le jeton d'accès pour récupérer les informations du profil de l'utilisateur auprès de GitHub.
- Votre site crée un nouveau compte utilisateur ou met à jour un compte existant, puis connecte l'utilisateur à partir des informations reçues de GitHub.
- Votre site redirige l'utilisateur vers la page appropriée après la connexion.
- L'utilisateur est connecté et peut interagir avec votre site.
Note
Notez que l'utilisateur est connecté à notre application, et non à GitHub. C'est essentiel pour éviter d'envoyer le jeton d'accès au client, pour gérer plusieurs fournisseurs ou pour ajouter un fournisseur à une authentification classique par e-mail et mot de passe.
Simple, n'est-ce pas ? Laravel propose un package officiel appelé Socialite, qui rend ce processus encore plus facile. Il gère les détails OAuth propres à chaque fournisseur afin que notre application puisse se concentrer sur ses propres utilisateurs.
Note
Gardez à l'esprit que l'authentification sociale n'est pas une solution miracle adaptée à tous les projets. Même si elle peut être séduisante au premier abord, elle apporte aussi son lot de difficultés.
Ajouter Socialite au projet
Commençons par installer le package :
composer require laravel/socialiteConfigurez-le ensuite dans config/services.php :
return [
// ...
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('GITHUB_REDIRECT'),
],
];La fonction env lit ces valeurs depuis .env. Ajoutez les identifiants GitHub et l'URL de callback :
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
GITHUB_REDIRECT=http://localhost:8000/auth/github/callbackL'utilisateur est connecté à travers notre application, et non à travers GitHub. Cette distinction est importante : nous n'envoyons pas le jeton d'accès du fournisseur au client et nous pourrons prendre en charge plusieurs fournisseurs d'authentification plus tard.
À ce stade, deux questions reviennent généralement :
- Comment obtenir
GITHUB_CLIENT_IDetGITHUB_CLIENT_SECRET? - Pourquoi avons-nous besoin de l'URL
GITHUB_REDIRECTet comment l'obtenir ?
GitHub fournit GITHUB_CLIENT_ID et GITHUB_CLIENT_SECRET lorsque vous créez une application OAuth. Ouvrez vos paramètres GitHub, accédez à Developer settings, sélectionnez OAuth Apps, puis cliquez sur New OAuth App.
Note
Une application OAuth utilise le protocole OAuth pour authentifier les utilisateurs. Dans notre cas, GitHub joue le rôle de fournisseur OAuth. Une application OAuth nous permet d'utiliser le système d'authentification de GitHub pour connecter les utilisateurs à notre site.
Remplissez le formulaire. GitHub est assez permissif avec les valeurs fournies pendant le développement local.
Application namepeut contenir n'importe quelle valeur. Je vous recommande d'utiliser le nom de votre site, car il est affiché aux utilisateurs lorsqu'ils se connectent, au moins la première fois.Homepage URLest l'URL de votre site. Vous pouvez utiliserhttp://localhost:8000en développement local.Authorization callback URLest l'adresse vers laquelle GitHub redirige les utilisateurs après l'autorisation de l'application. Elle doit correspondre à la valeur de.env. En développement local, utilisezhttp://localhost:8000/auth/github/callback.
Cliquez sur Register application pour créer l'application OAuth. Sa page contient le Client ID, qui ressemble à ceci :
a1b2c3d4e5f6g7h8i9j0Vous devez générer un nouveau Client secret. Cliquez sur Generate a new client secret et copiez la valeur. Elle ressemble à ceci :
d13f84af84688a69a71bc8b3808f8836e4c23774Ajoutez enfin ces valeurs à .env :
GITHUB_CLIENT_ID=a1b2c3d4e5f6g7h8i9j0
GITHUB_CLIENT_SECRET=d13f84af84688a69a71bc8b3808f8836e4c23774
GITHUB_REDIRECT=http://localhost:8000/auth/github/callbackN'oubliez pas d'ajouter également les variables d'environnement dans votre fichier .env.example.
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_REDIRECT=http://localhost:8000/auth/github/callbackNe commitez pas le client ID ni le secret. Laissez ces valeurs vides dans .env.example, mais conservez l'URL de redirection afin que toute personne qui clone le dépôt sache comment configurer une application OAuth locale.
Maintenant que tout est configuré, écrivons le code qui gère l'authentification.
Gérer l'authentification
Pour la suite du projet, nous utiliserons une approche centrée sur les tests : écrire un test, observer son échec, puis ajouter uniquement le code nécessaire pour le faire réussir. Cette méthode nous aide à définir le comportement avant l'implémentation et évite les surprises. Elle peut sembler intimidante au début, mais Laravel rend le workflow accessible grâce à ses outils de test intégrés.
Note
Il s'agit peut-être de développement piloté par les tests (TDD), mais le nom est moins important ici que le workflow : écrire d'abord le test, puis implémenter uniquement ce qui est nécessaire pour le faire réussir.
La redirection
Créons un nouveau contrôleur appelé RegisteredViaGitHubController :
php artisan make:controller RegisteredViaGitHubControllerCe contrôleur contiendra deux méthodes :
create: Gère la redirection vers GitHub.store: Gère le callback de GitHub.
J'utilise généralement des noms de méthodes traditionnels pour faciliter la lecture et rester cohérent. Les garder en tête sera utile, car les tests seront organisés autour de ces méthodes.
Dans le dossier tests/Http, nous pouvons supprimer le fichier ExampleTest.php, puis créer un dossier RegisteredViaGitHub et un nouveau fichier de test CreateTest.php.
mkdir tests/Http/RegisteredViaGitHub
touch tests/Http/RegisteredViaGitHub/CreateTest.phpDans ce fichier, écrivons notre premier test. C'est vraiment passionnant !
<?php
declare(strict_types=1);
use Laravel\Socialite\Facades\Socialite;
test('redirects for authorization', function () {
Socialite::shouldReceive('driver->redirect')->once();
$this->get(route('auth.github.redirect'));
expect(true)->toBeTrue();
});Note
expect(true)->toBeTrue() fournit une assertion au test sans ajouter de comportement à vérifier. Si Socialite::shouldReceive('driver->redirect')->once() n'est pas satisfait, le mock fera de toute façon échouer le test.
C'est notre premier test, mais pas le plus simple. Il simule la façade Socialite et vérifie que redirect est appelé une fois. Nous utilisons un mock, car le test ne doit pas nous envoyer vers GitHub : il doit seulement vérifier que notre code demande à Socialite d'effectuer la redirection.
Lançons le test :
php artisan test tests/Http/RegisteredViaGitHub/CreateTest.phpIl échoue, comme prévu. Dans un workflow piloté par les tests, l'échec nous indique ce qu'il faut implémenter ensuite. Suivons le message d'erreur et ajoutons uniquement le code nécessaire pour faire réussir le test.
➜ php artisan test tests/Http/RegisteredViaGitHub/CreateTest.php
FAILED Tests\Http\RegisteredViaGitHub\CreateTest > redirects for authorization
Route [auth.github.redirect] not defined.Le message est clair : nous devons définir la route auth.github.redirect. Faisons-le dans routes/web.php :
<?php
declare(strict_types=1);
use App\Http\Controllers\RegisteredViaGitHubController;
use Illuminate\Support\Facades\Route;
// other routes...
Route::get('/auth/github/redirect', [RegisteredViaGitHubController::class, 'create'])
->name('auth.github.redirect');Mais qu'est-ce que web.php ? C'est le fichier dans lequel nous définissons les routes accessibles depuis un navigateur et associons les URL aux méthodes des contrôleurs. Sa lecture offre une vue d'ensemble utile des points d'entrée de l'application.
Tip
Je vous recommande vivement de regarder Lessons From the Framework de Luke Dowing pour comprendre l'importance de ce fichier.
Cette route répond à /auth/github/redirect et appelle la méthode create de RegisteredViaGitHubController. Nous lui donnons également le nom auth.github.redirect, ce qui permet de la référencer dans le test et ailleurs dans l'application.
Relançons le test :
➜ php artisan test tests/Http/RegisteredViaGitHub/CreateTest.php
FAILED Tests\Http\RegisteredViaGitHub\CreateTest > redirects for authorization
Method redirect(<Any Arguments>) from Mockery_1__demeter_c357969a11a830028afbe426ab0eea17_driver should be called
exactly 1 times but called 0 times.Le nouveau message signifie que la route fonctionne, mais que le contrôleur n'appelle pas encore Socialite. Ajoutons cet appel au contrôleur :
<?php
namespace App\Http\Controllers;
use Laravel\Socialite\Facades\Socialite;
class RegisteredViaGitHubController extends Controller
{
public function create()
{
return Socialite::driver('github')->redirect();
}
}Relançons le test :
➜ php artisan test tests/Http/RegisteredViaGitHub/CreateTest.php
PASS Tests\Http\RegisteredViaGitHub\CreateTest
✓ redirects for authorizationEt voilà ! Le test réussit. Démarrez le serveur de développement avec composer dev et ouvrez http://localhost:8000/auth/github/redirect pour vérifier manuellement la redirection. Vous devriez arriver sur la page d'autorisation de GitHub. 🥳

Le callback
Maintenant que vous comprenez le workflow, je ne répéterai pas l'instruction de lancer le test après chaque petite modification. Continuez à suivre cette boucle pour le reste du projet et dans vos propres projets.
Créons un fichier de test StoreTest.php dans le dossier tests/Http/RegisteredViaGitHub.
touch tests/Http/RegisteredViaGitHub/StoreTest.phpStoreTest.php testera la méthode store de RegisteredViaGitHubController, qui gère le callback de GitHub. Socialite s'occupe de l'échange OAuth ; notre application doit donc seulement récupérer l'utilisateur du fournisseur, l'enregistrer et l'authentifier.
Commençons par écrire un test qui vérifie qu'un utilisateur peut être connecté :
<?php
declare(strict_types=1);
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\User as SocialiteUser;
test('registers successfully', function () {
$fakeGitHubUser = fakeGitHubUser();
$this->get(route('auth.github.callback'));
$this->assertAuthenticated();
$this->assertDatabaseHas('users', [
'github_id' => $fakeGitHubUser->getId(),
'email' => $fakeGitHubUser->getEmail(),
'name' => $fakeGitHubUser->getName(),
]);
});
/**
* Create a fake GitHub user and mock the Socialite driver.
*
* @param array<string, mixed> $attributes
*/
function fakeGitHubUser(array $attributes = []): SocialiteUser
{
$fakeGitHubUser = (new SocialiteUser)->map(attributes: array_merge([
'id' => '12345',
'nickname' => 'testuser',
'email' => 'test@example.com',
'name' => 'Test User',
'avatar' => 'https://example.com/avatar.jpg',
], $attributes));
Socialite::shouldReceive('driver->user')->once()->andReturn($fakeGitHubUser);
return $fakeGitHubUser;
}Ce test crée un faux utilisateur GitHub et configure Socialite pour qu'il le retourne lorsque user est appelé. Nous évitons ainsi une vraie requête HTTP vers GitHub, tandis que l'aide fakeGitHubUser rend cette configuration réutilisable et le test lisible.
Nous appelons ensuite la route auth.github.callback et vérifions que l'utilisateur est authentifié et enregistré dans la base de données.
Suivons maintenant le message d'erreur du test.
- La route
auth.github.callbackn'est pas définie. Définissons-la dansroutes/web.php:
Route::get('/auth/github/callback', [RegisteredViaGitHubController::class, 'store'])
->name('auth.github.callback');- L'utilisateur n'est pas authentifié. Implémentons la méthode
storedu contrôleur :
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
final class RegisteredViaGitHubController extends Controller
{
public function store()
{
$githubUser = Socialite::driver('github')->user();
Auth::login($githubUser);
}
}Socialite::driver('github')->user() gère le flux OAuth et retourne l'utilisateur du fournisseur. Nous utilisons ensuite la façade Auth de Laravel pour connecter cet utilisateur.
- L'utilisateur n'est toujours pas authentifié. La méthode
loginattend un utilisateur de l'application qui implémenteAuthenticatable, mais nous lui passons un utilisateur Socialite.
Nous ne pouvons pas transmettre directement l'utilisateur Socialite à login. Nous devons d'abord créer ou récupérer un utilisateur de l'application, puis passer ce modèle à login.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
final class RegisteredViaGitHubController extends Controller
{
public function store()
{
$githubUser = Socialite::driver('github')->user();
$user = User::create([
'name' => $githubUser->getName(),
'email' => $githubUser->getEmail(),
'username' => $githubUser->getNickname(),
'github_id' => $githubUser->getId(),
'avatar' => $githubUser->getAvatar(),
]);
Auth::login($user);
}
}- L'utilisateur n'est toujours pas authentifié. Cette fois, consultez le fichier de log. Dans Laravel, le log se trouve dans
storage/logs/laravel.log:
[2025-02-28 06:51:21] testing.ERROR: SQLSTATE[HY000]: General error: 1 no such table: users (Connection: sqlite, SQL: insert into "users" ("name", "email", "updated_at", "created_at") values (Test User, test@example.com, 2025-02-28 06:51:21, 2025-02-28 06:51:21))Tip
N'hésitez pas à supprimer tout le contenu du fichier avant de relancer le test. Vous ne verrez ainsi que les logs pertinents.
Ce message est plus explicite que le précédent : la table users n'existe pas, la requête échoue donc avant l'authentification de l'utilisateur.
Avant de créer la table, inspectons les migrations de la base de données :
0001_01_01_000000_create_users_table: Cette migration crée la tableusers. La table existe dans le schéma, mais la base de test n'a pas encore exécuté la migration.0001_01_01_000001_create_cache_table: Cette migration crée la tablecache. Elle ne nous intéresse pas ici.0001_01_01_000002_create_failed_jobs_table: Cette migration crée la tablefailed_jobs. Elle ne nous intéresse pas non plus.
Bonne nouvelle : nous pouvons exécuter automatiquement les migrations avant chaque test afin de conserver une base propre. Ajoutez le trait RefreshDatabase à Pest.php :
<?php
declare(strict_types=1);
pest()->extend(Tests\TestCase::class)
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
->in('Http');- L'utilisateur n'est toujours pas authentifié. Consultez à nouveau le log :
testing.ERROR: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: users.password (Connection: sqlite, SQL: insert into "users" ("name", "email", "updated_at", "created_at") values (Test User, test@example.com, 2025-02-28 06:59:57, 2025-02-28 06:59:57))Cette erreur SQL indique que password ne peut pas être null, alors que l'authentification GitHub ne fournit pas de mot de passe. Nous devons adapter la migration à l'authentification OAuth.
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table): void {
$table->id();
$table->string('name')->nullable();
$table->string('username')->unique();
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->string('github_id')->nullable()->unique();
$table->string('avatar')->nullable();
$table->timestamps();
});
// other migrations...
}
};Dans cette migration, nous ajoutons trois colonnes :
username: Stocke le nom d'utilisateur GitHub.github_id: Stocke l'identifiant GitHub de l'utilisateur. Il est unique pour empêcher deux utilisateurs de l'application de partager le même compte GitHub et nullable afin de pouvoir ajouter d'autres méthodes d'authentification plus tard. Rendre une colonne nullable après coup est plus complexe avec SQLite, nous ajoutons donc cette contrainte maintenant.avatar: Stocke l'URL de l'avatar de l'utilisateur.
Note
Nous modifions directement le fichier de migration au lieu d'en créer un nouveau, car l'application n'est pas encore déployée. Si elle l'était, nous aurions créé une migration pour modifier la table.
- L'utilisateur n'est toujours pas authentifié. Déboguer l'authentification dans une application fraîche peut sembler répétitif, mais chaque erreur révèle une pièce manquante. Une fois le processus compris, il devient beaucoup plus simple.
Dans laravel.log, nous pouvons lire :
[2025-02-28 07:09:29] testing.ERROR: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: users.username (Connection: sqlite, SQL: insert into "users" ("name", "email", "updated_at", "created_at") values (Test User, test@example.com, 2025-02-28 07:09:29, 2025-02-28 07:09:29))Cette erreur SQL ressemble à la précédente, mais cette fois username manque. C'est surprenant, car la migration et User::create contiennent tous deux ce champ.
Le problème vient de la protection contre l'assignation en masse de Laravel. Par défaut, nous devons autoriser explicitement username, github_id et avatar dans le modèle User :
<?php
declare(strict_types=1);
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'username',
'github_id',
'avatar',
'password',
];
// ...
}Cette approche fonctionne, mais elle oblige à modifier fillable chaque fois qu'une colonne assignable est ajoutée au modèle. La protection contre l'assignation en masse est particulièrement importante lorsque des données de requête sont transmises directement à un modèle. Par exemple, un utilisateur ne doit jamais pouvoir définir le champ is_admin lors de son inscription.
function (Request $request): void {
User::create($request->all()); // Never write this code.
}Sans cette protection, un utilisateur pourrait devenir administrateur en envoyant is_admin=true dans la requête. Ce serait un grave problème de sécurité.
Pour supprimer cette configuration répétitive tout en gardant les entrées sûres, nous pouvons désactiver le mode guarded avec Model::unguard() dans app/Providers/AppServiceProvider.php :
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Model::unguard();
}
}À partir de maintenant, tous les modèles seront non protégés et nous pouvons supprimer fillable de User. Cependant, toute entrée contrôlée par l'utilisateur doit passer par une form request. Valider les données avant de les enregistrer garantit que les utilisateurs ne peuvent pas définir des champs arbitraires ou des valeurs inattendues.
Dans ce flux OAuth, les données proviennent d'un fournisseur auquel nous faisons confiance, nous n'avons donc pas besoin de form request. Nous en utiliserons plus tard pour implémenter le système de commentaires.
Et voilà ! Le test réussit et les utilisateurs peuvent maintenant créer un compte avec GitHub. 🥳
Nous n'avons cependant pas terminé. Si un utilisateur se reconnecte, l'application tentera de créer un doublon. Nous devons gérer les utilisateurs existants.
Commençons par écrire un test pour vérifier qu'un utilisateur peut se reconnecter :
<?php
use App\Models\User;
// other tests...
test('updates the user if they already exist', function () {
$fakeGitHubUser = fakeGitHubUser();
$user = User::factory()->create(['github_id' => $fakeGitHubUser->getId()]);
$this->get(route('auth.github.callback'));
$this->assertAuthenticated();
$this->assertDatabaseHas('users', [
'id' => $user->id,
'github_id' => $fakeGitHubUser->getId(),
'email' => $fakeGitHubUser->getEmail(),
'name' => $fakeGitHubUser->getName(),
]);
});Ce test crée un utilisateur avec le même github_id que le faux utilisateur GitHub. Il appelle ensuite la route callback et vérifie que l'utilisateur existant est authentifié et mis à jour.
Suivons le message d'erreur :
SQLSTATE[HY000]: General error: 1 table users has no column named password (Connection: sqlite, SQL: insert into "users" ("name", "email", "email_verified_at", "password", "remember_token", "github_id", "updated_at", "created_at") values (Lawrence Herzog, roconnell@example.net, 2025-03-01 18:44:12, $2y$04$NXCZUWIcdKPiCdw3n5cTBOIpQo9/DPc9rmgX5VRTQ3iTeWduC4x36, 0i9tgsiLpA, 12345, 2025-03-01 18:44:12, 2025-03-01 18:44:12))Cette erreur SQL mentionne la colonne password. C'est étrange, puisque nous l'avons supprimée de la migration. L'erreur vient de la factory utilisateur, qui contient toujours les champs de mot de passe par défaut de Laravel. Ouvrez database/factories/UserFactory.php et supprimez-les :
<?php
declare(strict_types=1);
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/** // [!code --]
* The current password being used by the factory. //
*/
protected static ?string $password = null;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'username' => fake()->unique()->userName(),
'email' => fake()->unique()->safeEmail(),
'github_id' => fake()->unique()->numberBetween(1, 1000000),
'avatar' => fake()->imageUrl(),
'email_verified_at' => now(),
'password' => self::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/** // [!code --]
* Indicate that the model's email address should be unverified. //
*/
public function unverified(): static
{
return $this->state(fn (array $attributes): array => [
'email_verified_at' => null,
]);
}
}Après cette modification, l'erreur devient The user is not authenticated. Nous savons ce que cela signifie : il reste un autre problème à corriger.
En consultant laravel.log, nous découvrons le problème que nous cherchions :
[2025-03-01 18:51:14] testing.ERROR: SQLSTATE[23000]: Integrity constraint violation: 19 UNIQUE constraint failed: users.github_id (Connection: sqlite, SQL: insert into "users" ("name", "email", "username", "github_id", "avatar", "updated_at", "created_at") values (Test User, test@example.com, testuser, 12345, https://example.com/avatar.jpg, 2025-03-01 18:51:14, 2025-03-01 18:51:14))Cette erreur SQL signifie qu'un utilisateur avec le même github_id existe déjà. Au lieu de toujours créer un nouvel utilisateur, nous devons mettre à jour l'enregistrement existant lorsqu'il est trouvé et en créer un nouveau dans le cas contraire. La méthode updateOrCreate de Laravel fait exactement cela.
<?php
class RegisteredViaGitHubController extends Controller
{
public function store()
{
$githubUser = Socialite::driver('github')->user();
$user = User::updateOrCreate(
[
'github_id' => $githubUser->getId(),
],
[
'name' => $githubUser->getName(),
'email' => $githubUser->getEmail(),
'username' => $githubUser->getNickname(),
'avatar' => $githubUser->getAvatar(),
]
);
Auth::login($user);
}
}Redirigeons enfin l'utilisateur vers la page d'accueil du front-end après sa connexion.
Le dernier test vérifie que l'utilisateur est redirigé vers la page d'accueil après sa connexion :
<?php
// other tests...
test('redirects to the homepage', function () {
fakeGitHubUser();
$this->get(route('auth.github.callback'))
->assertRedirect('/');
});La méthode assertRedirect vérifie que l'utilisateur est redirigé vers la page d'accueil après sa connexion.
Il ne reste plus qu'à implémenter la redirection dans le contrôleur :
<?php
class RegisteredViaGitHubController extends Controller
{
public function store()
{
// ...
Auth::login($user);
return redirect('/');
}
}Et voilà, le contrôleur est terminé !
Atteindre une couverture complète
Avant de conclure, vérifions que le script de test s'exécute correctement.
Tip
Avant de lancer le script de test, exécutez d'abord les scripts de lint et de refactorisation.
PHPStan n'est pas satisfait de notre code. Il signale deux erreurs :
------ ---------------------------------------------------------------------------------------------------
Line app/Http/Controllers/RegisteredViaGitHubController.php
------ ---------------------------------------------------------------------------------------------------
:13 Method App\Http\Controllers\RegisteredViaGitHubController::create() has no return type specified.
🪪 missingType.return
:18 Method App\Http\Controllers\RegisteredViaGitHubController::store() has no return type specified.
🪪 missingType.return
------ ---------------------------------------------------------------------------------------------------Les méthodes n'ont pas de type de retour. Ajoutez : RedirectResponse aux deux méthodes :
<?php
use Symfony\Component\HttpFoundation\RedirectResponse;
class RegisteredViaGitHubController extends Controller
{
public function create(): RedirectResponse
{
// ...
}
public function store(): RedirectResponse
{
// ...
}
}Le script test:unit échoue encore, car cette route par défaut n'a pas de test :
Route::get('/', function () {
return view('welcome');
});Nous pouvons la supprimer sans risque, car les utilisateurs doivent être redirigés vers la page d'accueil de l'application front-end, et non vers celle de l'application Laravel.
Une fois cette route supprimée, tout est au vert ! 🟢
J'adore vraiment, vraiment cette sensation.
Dernières réflexions
L'authentification GitHub est maintenant en place. Nous avons un modèle User local, un flux OAuth complet et des tests qui décrivent le comportement attendu. Le workflow piloté par les tests peut sembler plus lent au début, mais il nous donne confiance à mesure que l'implémentation grandit.
Socialite gère les détails propres au fournisseur afin que nous puissions nous concentrer sur les utilisateurs et les sessions de notre application. Le système de commentaires dispose ainsi d'un propriétaire fiable pour chaque commentaire.
Ensuite, nous allons concevoir et implémenter l'API Laravel permettant de lister, créer, modifier et supprimer des commentaires.
Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web et le parcours humain qui l'entoure.
Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.
Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.
J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous, cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !
Discussions
Ajouter un commentaire
Vous devez être connecté pour accéder à cette fonctionnalité.