Desarrollo Web 5-8 minutos

Login social en Laravel 13 con Socialite: Google y GitHub paso a paso

Diego Cortés
Diego Cortés
Full Stack Developer & SEO Specialist
Compartir:
Login social en Laravel 13 con Socialite: Google y GitHub paso a paso

El login social en Laravel 13 con Socialite se reduce a dos métodos, redirect() y user(), pero la configuración esconde el trabajo: credenciales, redirect URIs, scopes y la creación del usuario local. Esta guía recorre Google y GitHub de principio a fin.

Qué es Socialite y por qué usarlo

Laravel Socialite es el paquete oficial del ecosistema que envuelve las librerías OAuth 1 y OAuth 2. En lugar de implementar a mano el intercambio de código por token, el estado anti-CSRF o el parsing de respuestas, te deja dos llamadas: redirigir al proveedor y recuperar el perfil al volver. A mediados de 2026 supera los 84 millones de descargas en Packagist, su rama actual es la v5.x y es compatible con Laravel 13, lanzado el 17 de marzo de 2026 con PHP 8.3 como mínimo.

OAuth 1 y OAuth 2 sin boilerplate: dos métodos en vez de diez pasos

Sin Socialite, "Entrar con Google" implica montar a mano la URL de autorización, validar el parámetro state, intercambiar el código por un token y pedir el perfil. Con Socialite, el controlador de redirección devuelve una respuesta que manda al usuario a Google, y el callback recibe un objeto con los datos del perfil. Todo el baile HTTP queda dentro del paquete y tu código solo decide qué hacer con ese usuario.

Proveedores oficiales y de la comunidad (SocialiteProviders)

La documentación de Laravel 13 lista de serie Facebook, X, LinkedIn, Google, GitHub, GitLab, Bitbucket y Slack, y el README añade Twitch. Para el resto, el ecosistema SocialiteProviders mantiene decenas de adaptadores con la misma API: instalas el paquete, registras su listener y usas el driver con el patrón de siempre. Una sola curva de aprendizaje, da igual el proveedor.

Instalación y configuración

Instalar Socialite es un comando de Composer y, después, dos bloques de configuración: uno en config/services.php y otro en el panel del proveedor.

composer require laravel/socialite y la entrada en config/services.php

composer require laravel/socialite:^5.0

Las credenciales se guardan en config/services.php bajo la clave de cada proveedor, siempre con variables de entorno y nunca en duro:

'google' => [
    'client_id' => env('GOOGLE_CLIENT_ID'),
    'client_secret' => env('GOOGLE_CLIENT_SECRET'),
    'redirect' => env('GOOGLE_REDIRECT_URI'),
],

'github' => [
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'redirect' => env('GITHUB_REDIRECT_URI'),
],

El campo redirect es la URL de callback del flujo y debe coincidir exactamente con la URI autorizada en el proveedor. Es el primer sitio donde mirar cuando algo falla.

Credenciales en Google Cloud Console: OAuth client ID y redirect URIs

En Google Cloud Console creas un proyecto, activas la pantalla de consentimiento de OAuth y creas una credencial de tipo "OAuth client ID" para aplicación web. Google entrega el client ID y el client secret, y te pide registrar las redirect URIs autorizadas: ahí pones tu callback, por ejemplo https://tu-blog.com/auth/callback/google.

Credenciales en GitHub OAuth Apps

En GitHub el equivalente son las OAuth Apps: Settings, Developer settings, OAuth Apps, y creas una nueva. Registras la Homepage URL y la Authorization callback URL, y GitHub te da el client ID y el secret al instante. A diferencia de Google, no hay pantalla de consentimiento previa: el usuario autoriza la app en el momento del login.

El error más común: redirect_uri no coincide con la URI autorizada

El fallo típico de cualquier integración OAuth es el "Invalid redirect_uri" o el "Missing required parameter client_id". La causa casi siempre es la misma: el valor redirect de config/services.php no coincide carácter a carácter con la URI autorizada, o las variables de entorno del client_id no están definidas y Laravel manda una cadena vacía. Revisa el .env, después la URI registrada, y ejecuta php artisan config:clear tras tocar cualquier valor.

Las dos rutas del flujo OAuth

El flujo se monta con dos rutas: una que redirige al proveedor y otra que recibe al usuario de vuelta. En web.php, con GitHub como ejemplo:

Route::get('/auth/redirect', function () {
    return Socialite::driver('github')->redirect();
});

Route::get('/auth/callback', function () {
    $user = Socialite::driver('github')->user();

    // Crear o actualizar el usuario local y hacer login
});

Redirigir al proveedor con ->redirect()

redirect() construye la URL de autorización, añade el parámetro state anti-CSRF y devuelve una respuesta de redirección. En la vista, el botón "Entrar con GitHub" es un enlace o formulario que apunta a /auth/redirect; el usuario autoriza en GitHub y vuelve con un código que tu callback intercambiará por el perfil.

Recibir al usuario en el callback con ->user()

En el callback, user() intercambia el código por un access token y pide el perfil al proveedor. El objeto resultante expone token, refreshToken (no siempre disponible) y expiresIn, además de los datos del perfil. Si el intercambio falla, captura la excepción y muestra un mensaje amable en lugar de un error 500.

Crear o actualizar el usuario local

Con el perfil del proveedor en la mano, toca decidir qué pasa en tu base de datos. El patrón recomendado para un blog como blenderdeluxe: crear el usuario si es la primera vez o actualizar sus datos si ya existe, y hacer login a continuación.

Los datos de $user: id, nombre, email y avatar

El objeto devuelto por user() se lee con métodos: getId(), getName(), getEmail(), getAvatar() y getNickname(). Con eso rellenas tu tabla users; el avatar merece un apunte en la sección de seguridad, porque guardar la URL remota tal cual no es buena idea.

Vincular por provider_id y provider, no solo por email

La clave de un login social correcto es vincular la cuenta local por el par provider y provider_id, y no solo por email: el email puede no estar verificado por el proveedor, o venir en null (el caso de GitHub). El patrón con updateOrCreate:

$user = User::updateOrCreate(
    ['provider' => 'github', 'provider_id' => $githubUser->getId()],
    [
        'name' => $githubUser->getName() ?? $githubUser->getNickname(),
        'email' => $githubUser->getEmail(),
        'avatar' => $githubUser->getAvatar(),
    ]
);

Auth::login($user);

return redirect('/dashboard');

Vincular por provider_id hace que, si el email del proveedor cambia, tu usuario local siga siendo el mismo; y evita que un atacante con un email no verificado asocie su cuenta a la de otra persona.

Scopes, dominios y stateless

Por defecto Socialite pide los scopes básicos del proveedor. Cuando necesitas más datos, o quieres limitar quién puede entrar, entran en juego tres herramientas.

scopes() y setScopes(): pedir solo lo que necesitas

scopes() añade permisos a los que el driver pide por defecto, y setScopes() los sustituye por completo. Por ejemplo, para pedir los repos públicos de GitHub en la misma autorización:

return Socialite::driver('github')
    ->scopes(['read:user', 'public_repo'])
    ->redirect();

La regla es pedir el mínimo: cuantos más scopes, más recelo genera la pantalla de consentimiento y más superficie de riesgo acumulas.

Restringir a un dominio corporativo con with(['hd' => ...])

Para una app interna de una empresa con Google Workspace, limitas el login a un dominio concreto con el parámetro hd (hosted domain):

return Socialite::driver('google')
    ->with(['hd' => 'tuempresa.com'])
    ->redirect();

Si el usuario intenta entrar con una cuenta de otro dominio, Google lo rechaza en su propia pantalla, antes de llegar a tu callback.

stateless() para APIs y SPA sin sesión

Cuando el frontend es una SPA o una API sin sesiones, el parámetro state no tiene dónde guardarse. Socialite ofrece stateless() para ese escenario: el flujo se completa sin depender de la sesión. Es el patrón típico de login social desde frontends desacoplados y combina con los tokens de Laravel 13 con Sanctum que ya vimos en este blog.

$user = Socialite::driver('google')->stateless()->user();

El gotcha de GitHub: emails privados

GitHub permite ocultar el email en el perfil. Cuando ocurre, getEmail() devuelve null y tu updateOrCreate guardaría un email vacío, rompiendo cualquier lógica que dependa de él. La solución: pedir el scope user:email, que autoriza a leer la dirección aunque sea privada, o tratar el null explícitamente, por ejemplo redirigiendo a un formulario que pida el email en un segundo paso. La peor opción es asumir que el email siempre llega.

Seguridad en el login social

Socialite gestiona por ti el parámetro state anti-CSRF, así que el eslabón débil no suele estar en el paquete, sino en lo que haces con los datos que recibes.

Validar emails verificados y no confiar en avatares remotos

Si tu app considera el email verificado, asegúrate de que el proveedor lo confirma: en Google el ID token trae email_verified, y en GitHub el email viene verificado por defecto cuando es público. Además, no uses la URL del avatar remoto en tus vistas: descarga la imagen a tu propio almacenamiento en el alta, como vimos en la guía de subida de archivos de este blog, y guarda la ruta local en la tabla users.

Cuentas duplicadas y rotación de tokens

Dos proveedores distintos pueden devolver el mismo email para cuentas diferentes: por eso el par provider/provider_id es la única clave fiable. Guarda el access token solo si lo vas a usar; si no, no lo persistas. Y recuerda que expira: tu app debe renovarlo con el refreshToken antes de usarlo.

Testing del flujo con Socialite::fake()

Testear el callback con llamadas HTTP reales es lento y frágil. Socialite trae Socialite::fake(), que intercepta las llamadas del paquete y devuelve el usuario que definas. Un test con Pest o PHPUnit:

use Laravel\Socialite\Facades\Socialite;

Socialite::fake([
    'github' => Socialite::userFromToken('fake-token')
        ->setId('12345')
        ->setName('Diego')
        ->setEmail('diego@example.com'),
]);

$response = $this->get('/auth/callback');

$response->assertRedirect('/dashboard');
$this->assertDatabaseHas('users', [
    'provider' => 'github',
    'provider_id' => '12345',
]);

Con esto cubres el caso feliz, el usuario que ya existía, el email en null y el login fallido, sin depender de la red ni de cuentas reales de prueba.

Conclusión

El login social en Laravel 13 con Socialite convierte un flujo OAuth que a mano ocuparía cientos de líneas en dos rutas y un updateOrCreate. Instala el paquete, configura las credenciales de Google y GitHub en services.php, vincula por provider_id y no olvides el email privado de GitHub ni el testing con Socialite::fake(). Si te quedas con ganas de más, sigue leyendo el blog: hay guías de desarrollo web con Laravel 13 cada semana.

Categorías