Por qué el acceso social duplica cuentas y cómo lo arregla una tabla de bloqueo
- Publicado el
- • 4 min de lectura•--- vistas
El acceso social parece un problema resuelto. El proveedor redirige de vuelta con un código, lo intercambias por un perfil, buscas o creas un usuario, lo autenticas. Cuatro pasos, todos documentados.
Y entonces soporte empieza a fusionar clientes duplicados a mano.
El fallo
Una llamada de retorno de autorización es una petición HTTP corriente, y las peticiones HTTP corrientes ocurren más de una vez. El usuario pulsa dos veces el botón de confirmar del proveedor. Una conexión móvil inestable hace que el navegador reintente. Un precargador de enlaces calienta la URL antes de que el dedo termine el gesto. Cualquiera de estos casos entrega la misma llamada dos veces, con milisegundos de diferencia.
Ambas peticiones llevan el mismo código de autorización. Ambas lo intercambian. Ambas buscan al usuario por correo. Ninguna encuentra nada, porque ninguna ha terminado de escribir todavía. Ambas crean una cuenta.
Ahora tienes dos clientes con la misma dirección de correo. Si tu proveedor no devuelve correo, tienes dos cuentas con nombres de acceso casi idénticos y ninguna forma de saber cuál guarda el historial de pedidos.
Por qué el arreglo obvio no arregla nada
El instinto es proteger la escritura:
if (!email_exists($email)) {
wp_insert_user([...]);
}
Esto no ayuda, y entender por qué es justamente lo importante. La comprobación y la escritura son dos operaciones separadas contra la base de datos, y nada impide que la segunda petición ejecute su comprobación en el hueco entre la comprobación y la escritura de la primera. La ventana es pequeña. No es cero, y las llamadas llegan exactamente en las ráfagas que la encuentran.
Un índice único sobre la columna de correo sí es una mejora real: la base rechazará la segunda inserción. Pero convierte un problema de datos en una página de error: ahora falla la segunda petición, que es precisamente la que el navegador del usuario está mirando. Ve un error después de un acceso correcto.
Reservar el código, no el usuario
La clave está en que lo genuinamente único aquí no es el usuario. Es el código de autorización. Los proveedores emiten un código por intento de acceso, y es de un solo uso por diseño.
Así que resérvalo antes de hacer cualquier otra cosa:
public function acquire(string $code): bool
{
if ($this->checkCodeIsProcessing($code)) {
return false; // alguien ya se ocupa de este acceso
}
$this->setCodeIsProcessing($code, true);
return true;
}
El manejador de la llamada queda así:
$code = $request->get_param('code');
if (!$this->acquire($code)) {
return $this->redirectByCode($code); // seguir al ganador
}
$user = $service->authClientByCode($code);
$wp_user_id = $user->findOrCreateWpUser();
return $this->redirectByCodeAndUser($code, $wp_user_id);
La primera petición gana la reserva y hace el trabajo. La segunda pierde y, en lugar de fallar, espera a que la ganadora registre el user_id resultante y el destino de redirección, y luego emite esa misma redirección. Los dos navegadores acaban autenticados como la misma persona. Un acceso, una cuenta, ninguna página de error.
La fila de reserva lleva un contador en vez de un booleano, así que solo se elimina cuando se han atendido todas las peticiones esperadas. Eso importa cuando un proveedor llama legítimamente más de una vez.
Lo que esto no resuelve
Dos limitaciones honestas.
No es un bloqueo distribuido. checkCodeIsProcessing y setCodeIsProcessing siguen siendo dos sentencias. Sobre una única base de datos, con peticiones separadas por milisegundos, cierra la ventana en la práctica; con concurrencia real a escala conviene que la unicidad la imponga la base: un índice único sobre la columna del código, la propia inserción como reserva y una violación de restricción capturada como señal de «has perdido».
La petición que espera duerme. Seguir al ganador implica darle tiempo para terminar. Eso es una pausa fija, es decir, algo tosco: demasiado corta y el seguidor no encuentra nada, demasiado larga y un usuario espera sin motivo. Está acotada y recae solo sobre la petición perdedora, pero es un compromiso.
La forma general
Este patrón no va de OAuth. Aparece siempre que un sistema externo te llama de vuelta sin garantía de entrega exactamente una vez: notificaciones de pago, entrega de webhooks, consumidores de colas.
La regla es la misma cada vez: encuentra el identificador que el sistema externo considera único, resérvalo en un solo sitio antes de hacer trabajo alguno, y haz que el perdedor siga al ganador en lugar de fallar. Deduplicar por el resultado (el usuario, el pedido, el registro) siempre es una carrera, porque en el momento en que necesitas comprobarlo el resultado todavía no existe.
Relacionado
Proyectos

CactusPlay - Tienda de juegos con una red de colaboradores verificados
Antes: los compradores de la CEI comparaban a mano los precios de PlayStation y Xbox entre vendedores dispersos, y cada pedido lo cumplían los propios fundadores. Después: una tienda con catálogo en vivo y un CRM al que podía sumarse cualquier colaborador verificado con datos de pago turcos para tomar pedidos y ganar con ellos. Resultado: más de 20 000 posiciones por hora, cambios de precio que llegan a la tienda y a Yandex.Market sin intervención humana, y un cumplimiento que ya no depende de un solo par de manos.
Artículos
Que la entrega sea consecuencia del pago, no una tarea que alguien ejecuta
La entrega manual de productos digitales está limitada por el horario laboral y protegida solo por la atención de alguien. Mover la entrega a los eventos del pedido cambia lo que el sistema garantiza, y la API es lo que impide que un segundo canal se convierta en una segunda implementación.
Poner precio a un catálogo contra una tienda que no controlas
Revender significa que tus precios dependen de que otro cambie los suyos. Consultar al cargar la página tumba tu tienda con la suya; una exportación nocturna se queda obsoleta. El camino intermedio es una caché con una estrategia de precio detrás.
Un widget de soporte que ya sabe con quién habla
Pegar un widget de chat en una plantilla funciona hasta que el tema se actualiza, hasta que hay que cambiar un ajuste o hasta que el widget añade una consulta a la base de datos en cada página. Tres decisiones pequeñas lo arreglan todo.
Disponible para colaboración por contrato
Estoy disponible para colaborar por contrato. Si tiene una idea de proyecto interesante, reserve una llamada por Calendly.
Agenda una llamada de 30 min