Skip Content

A01: Control de Acceso Roto (Blog)

¿Qué es el control de acceso roto?

El Control de Acceso Roto ocurre cuando una aplicación permite que un usuario realice acciones o acceda a datos que no debería poder. En un blog suele verse cuando se considera suficiente que el usuario esté autenticado, pero no se aplican de forma consistente las reglas de autor (solo autores pueden escribir posts) y la propiedad del recurso.

Fallo típico en Laravel (blog)

  • Verificas auth() pero olvidas la regla de negocio (solo autores pueden crear posts).
  • Permites que usuarios editen posts/comentarios que no les pertenecen.
  • Expones IDs y permites que usuarios accedan a registros que no les pertenecen.
  • Olvidas checks de policies en Actions, controllers, componentes Livewire o endpoints API.

Impacto

  • Escalamiento horizontal de privilegios (leer/actualizar/eliminar registros de otros usuarios).
  • Fuga de contenido privado/borradores.
  • Enumeración de IDs (adivinar IDs para descubrir recursos ocultos).

Remediación en Laravel 12 (reglas del blog)

Laravel 12 documenta oficialmente Policies y ofrece denyAsNotFound() como un patrón de primera clase.

  • Lectura pública: permitir viewAny / view para posts y comentarios (incluye guests).
  • Escritura de posts: restringir create / update a usuarios autorizados (rol/permisos).
  • Escritura de comentarios: permitir create para cualquier usuario autenticado.
  • Edición de comentarios: restringir a owner (o admin).
  • Usa Gates para permisos transversales (roles globales, admin-only, feature flags).
  • Prefiere responder 404 en lugar de 403 cuando exista riesgo de enumeración, usando denyAsNotFound().
  • Aplica scope por usuario/propiedad antes de obtener registros.
  • En Actions, la autorización debe ser el primer invariante.

Arreglo concreto

Solo usuarios autorizados pueden crear posts:

use App\Example\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
 
public function handle(User $user, array $data): Post
{
Gate::authorize('create', Post::class);
 
return Post::query()->create([
'user_id' => $user->id,
'title' => $data['title'],
'body' => $data['body'] ?? null,
'status' => 'draft',
]);
}

Lectura publica: no protejas rutas de lectura con auth (todos pueden leer posts/comentarios):

use Illuminate\Support\Facades\Route;
 
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);

Enfoque de patrón de diseño

Si tu base de código usa Actions, aplica un wrapper tipo Proxy/Decorator para reforzar permisos de forma repetible. Evita dispersar checks como if ($user->id !== ...) en controllers: eso degrada la arquitectura.

use Illuminate\Support\Facades\Gate;
 
final class Authorize
{
public static function for(string $ability, mixed $resource, callable $next): mixed
{
Gate::authorize($ability, $resource);
 
return $next();
}
}
 
// Ejemplo (Action)
final class UpdatePostAction
{
public function handle(User $user, Post $post, array $data): void
{
$post->fill($data)->save();
}
}
 
// En un controller / endpoint
$action = app(UpdatePostAction::class);
 
return Authorize::for('update', $post, function () use ($action, $user, $post, $data) {
$action->handle($user, $post, $data);
 
return redirect()->back();
});

Referencias