SAML: IMPLEMENTACIÓN DE SINGLE SIGN-ON EN LARAVEL

SAML, Security Assertion Markup Language, es un estándar que se basa en XML, que permite el intercambio seguro de información de autenticación y autorización entre un Identity Provider (IdP) y un Service Provider (SP).
  • Identity Provider (IdP): Sistema que autentica al usuario como por ejemplo: Azure AD, Okta, Google Workspace, ADFS, Keycloak, OneLogin).
  • Service Provider (SP): La aplicación PHP que consume la autenticación.

Ventajas de usar SAMLSingle Sign-On

  • Mayor seguridad ya que las credenciales no viajan entre sistemas.
  • Descentralización de la gestión de usuarios.
  • Soporte para atributos como roles, departamentos, permisos, etc..
  • Ampliamente aceptado en entornos empresariales.

Implementación

La forma más limpia y moderna de implementar SAML, en Laravel es usando el siguiente paquete:

composer require "aacotroneo/laravel-saml2"

Instalación y configuración

composer require aacotroneo/laravel-saml2
Publicar la configuración
php artisan vendor:publish --provider="Aacotroneo\Saml2\Saml2ServiceProvider"

Configuración básica ("config/saml2_settings.php")

return [
    'default' => 'idp1',

    'idp1' => [
        'idp' => [
            'entityId' => 'https://idp.ejemplo.com/saml/metadata',
            'singleSignOnService' => [
                'url' => 'https://idp.ejemplo.com/saml/sso',
            ],
            'singleLogoutService' => [
                'url' => 'https://idp.ejemplo.com/saml/slo',
            ],
            'x509cert' => 'MIID...',
        ],
        'sp' => [
            'entityId' => 'https://miapp.com/saml/metadata',
            'assertionConsumerService' => [
                'url' => 'https://miapp.com/saml/acs',
            ],
            'singleLogoutService' => [
                'url' => 'https://miapp.com/saml/sls',
            ],
        ],
        'security' => [
            'nameIdEncrypted' => false,
            'authnRequestsSigned' => false,
            'logoutRequestSigned' => false,
            'logoutResponseSigned' => false,
            'signMetadata' => false,
            'wantMessagesSigned' => false,
            'wantAssertionsSigned' => true,
            'wantNameId' => true,
            'wantNameIdEncrypted' => false,
        ],
    ],
];

Rutas principales

// routes/web.php
Route::get('/saml/login', 'SamlController@login');
Route::get('/saml/metadata', 'SamlController@metadata');
Route::post('/saml/acs', 'SamlController@acs');
Route::get('/saml/sls', 'SamlController@sls');

Controlador de ejemplo

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Aacotroneo\Saml2\Saml2Auth;

class SamlController extends Controller
{
    protected $saml;

    public function __construct(Saml2Auth $saml)
    {
        $this->saml = $saml;
    }

    public function login()
    {
        return $this->saml->login(config('app.url') . '/dashboard');
    }

    public function acs()
    {
        $this->saml->processResponse();

        if ($this->saml->getLastError()) {
            return response()->json(['error' => $this->saml->getLastError()], 500);
        }

        $user = $this->saml->getSaml2User();

        // Atributos recibidos del IdP
        $attributes = $user->getAttributes();
        $nameId = $user->getNameId();

        // Crear o actualizar usuario en tu sistema
        $laravelUser = \App\Models\User::updateOrCreate(
            ['email' => $nameId],
            [
                'name' => $attributes['name'][0] ?? $nameId,
                // otros campos...
            ]
        );

        auth()->login($laravelUser);

        return redirect('/dashboard');
    }
}