Ir al contenido principal
Laravel, shipping fast.
Capítulo 4 · Autenticación

Credenciales por consumidor

Julian Beaujardin

Esta es la parte de la API que no está en ningún tutorial. Consumidores distintos actúan en nombre de cuentas de Statamic distintas. Cuando el servicio de aprovisionamiento de un socio crea una licencia, debe crearse con las credenciales de Statamic de ese socio y de nadie más.

Así que un consumidor lleva ajustes:

// app/Models/Consumer.php
protected function casts(): array
{
    return ['settings' => 'encrypted:array'];
}

public function providerToken(): string
{
    return $this->settings['statamic_token']
        ?? throw new MissingProviderCredentials($this->id);
}

encrypted:array significa que la columna guarda texto cifrado. El token de proveedor de un socio es tan sensible como el tuyo, y nunca es legible en un volcado de la base de datos.

providerToken() es el único código de la aplicación que lee esa clave, y fíjate en lo que hace cuando la clave falta. Lanza una excepción. No recurre a un token compartido de la configuración.

Esa es la línea más importante de este capítulo. Un valor de reserva parece inofensivo y útil: un consumidor sin credenciales propias usa la cuenta de la casa y listo. También significa que todo consumidor cuyos ajustes estén vacíos, mal escritos o alterados por una migración aterriza en silencio en la misma cuenta, donde cada uno puede listar y borrar las licencias de los demás. Una API que aísla a sus inquilinos mediante credenciales tiene que fallar en cerrado, es decir, negarse, cuando las credenciales no están. No hay cuenta por defecto. El token compartido de config/services.php del Capítulo 3 se elimina.

El driver aprende a construirse para un consumidor:

// app/Services/License/StatamicDriver.php
public static function for(Consumer $consumer): self
{
    return new self($consumer->providerToken());
}

Y el binding del Capítulo 3 cambia en dos cosas:

// app/Providers/AppServiceProvider.php, en register()
$this->app->scoped(LicenseContract::class, function () {
    $consumer = Auth::user()
        ?? throw new LogicException('No consumer.');

    return StatamicDriver::for($consumer);
});

Ningún controlador cambió. Ningún request, ningún Resource. Licenses::all() llega ahora al proveedor en nombre de quien esté llamando.

Esa es también la razón por la que las licencias no necesitan una Policy. Las peticiones de un consumidor se hacen con sus propias credenciales de proveedor, así que las licencias de otra cuenta no son alcanzables en ninguna URL. El aislamiento es exactamente tan fuerte como distintas sean las credenciales: dos consumidores con el mismo token de proveedor comparten cuenta, y eso nunca debería ocurrir por accidente.

La trampa del binding

El segundo cambio es la palabra scoped, y merece un párrafo.

En el Capítulo 3 el driver era un singleton. Un singleton vive tanto como el proceso. En un worker de colas, o bajo Octane, un mismo proceso atiende a muchos consumidores, y el driver construido para el primero se reutilizaría para el segundo, con las credenciales del primero.

Un binding con ámbito (scoped) es un singleton para una sola petición o un solo job. Laravel lo descarta entre uno y otro. Siempre que algo del contenedor dependa de quién pregunta, no debe sobrevivir a la pregunta. (Documentación de Laravel: Service Container › Binding Scoped Singletons.)

Un job encolado no tiene ningún usuario autenticado. El Capítulo 10 muestra cómo el job lleva consigo a su consumidor.

No se pudo cargar el audio. Inténtalo de nuevo en un momento.