
Symfony:api Platform Security Skill
- 439 installs
- 190 repo stars
- Updated August 6, 2026
- makfly/superpowers-symfony
symfony:api-platform-security is a Claude Code agent skill that secures Symfony API Platform resources with security expressions, voters, securityPostValidation, and operation-level access control for developers shipping
About
symfony:api-platform-security is a Claude Code skill from makfly/superpowers-symfony for securing API Platform contracts in Symfony projects. It guides operation-level payload boundaries, resource and DTO provider/processor changes, operation-specific validation, and security constraints across happy and negative paths. The default workflow defines the operation contract, implements explicit mapping without exposing internal entity fields, applies securityPostValidation and voters, and validates functional behavior. Guardrails keep API contracts version-aware and prevent drift between OpenAPI docs and actual serialization. Developers invoke it when designing or evolving API Platform operations that need authentication, authorization, and resource-level access rules before production exposure. Reference files include `reference.md` and `docs/complexity-tiers.md` for deeper implementation tiers.
- Configure API Platform operation security expressions
- Apply Symfony voters and role hierarchies to resources
- Integrate JWT, OAuth, or session auth with API routes
- Enforce field- and collection-level access policies
- Align OpenAPI docs with real auth requirements
Symfony:Api Platform Security by the numbers
- 439 all-time installs (skills.sh)
- Ranked #539 of 2,222 Security skills by installs in the Skillselion catalog
- Data as of Aug 11, 2026 (Skillselion catalog sync)
npx skills add https://github.com/makfly/superpowers-symfony --skill symfonyapi-platform-securityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 439 |
|---|---|
| repo stars | ★ 190 |
| Last updated | August 6, 2026 |
| Repository | makfly/superpowers-symfony ↗ |
How do you secure Symfony API Platform endpoints?
Harden Symfony API Platform endpoints with authentication, authorization, voters, and resource-level access rules before exposing production APIs.
Who is it for?
Symfony API Platform developers hardening JSON APIs with operation-level security expressions, voters, and securityPostValidation before production launch.
Skip if: Non-Symfony stacks, frontend-only apps without API Platform, or teams that only need generic OWASP checklists without PHP resource wiring.
When should I use this skill?
User asks to add authentication, authorization, voters, or resource-level access control to API Platform operations in a Symfony project.
What you get
Secured API Platform resources/DTOs, operation-level access rules, voter implementations, and verified negative-path authorization tests.
- Secured API operations
- Voter and expression configuration
- Authorization test results
By the numbers
- Default workflow has 4 steps: contract, implement, secure, validate
- References 2 deep-dive files: reference.md and docs/complexity-tiers.md
Files
Api Platform Security (Symfony)
Use when
- Designing or evolving API Platform contracts and operations.
- Aligning serialization, validation, and security behavior.
Default workflow
1. Define operation-level contract and payload boundaries. 2. Implement resource/DTO/provider/processor changes with explicit mapping. 2. Apply operation-specific validation and security constraints. 2. Validate functional behavior across happy and negative paths.
Guardrails
- Keep API contract explicit and version-aware.
- Avoid exposing internal entity fields implicitly.
- Prevent drift between docs and actual serialization.
Progressive disclosure
- Use this file for execution posture and risk controls.
- Open references when deep implementation details are needed.
Output contract
- API artifacts changed (resource/DTO/provider/processor).
- Contract/security decisions and rationale.
- Functional verification results.
References
reference.mddocs/complexity-tiers.md
Reference
API Platform Security
Operation-Level Security
Basic Security Expressions
<?php
// src/Entity/Post.php
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
#[ApiResource(
operations: [
// Public read access
new GetCollection(),
new Get(),
// Authenticated users can create
new Post(
security: "is_granted('ROLE_USER')",
securityMessage: 'You must be logged in to create posts.',
),
// Only owner or admin can update
new Put(
security: "is_granted('ROLE_ADMIN') or object.getAuthor() == user",
securityMessage: 'You can only edit your own posts.',
),
new Patch(
security: "is_granted('ROLE_ADMIN') or object.getAuthor() == user",
),
// Only admin can delete
new Delete(
security: "is_granted('ROLE_ADMIN')",
securityMessage: 'Only administrators can delete posts.',
),
],
)]
class Post
{
// ...
}Using Voters
#[ApiResource(
operations: [
new Get(
security: "is_granted('POST_VIEW', object)",
),
new Put(
security: "is_granted('POST_EDIT', object)",
securityMessage: 'You cannot edit this post.',
),
new Delete(
security: "is_granted('POST_DELETE', object)",
),
],
)]
class Post { /* ... */ }Security Post-Denormalization
Check security after input is processed:
#[ApiResource(
operations: [
new Post(
// Check before processing
security: "is_granted('ROLE_USER')",
// Check after input is bound to object
securityPostDenormalize: "is_granted('POST_CREATE', object)",
securityPostDenormalizeMessage: 'You cannot create this type of post.',
),
],
)]
class Post { /* ... */ }Useful when security depends on the input data itself.
Security Expressions Reference
// User roles
security: "is_granted('ROLE_USER')"
security: "is_granted('ROLE_ADMIN')"
// Current user
security: "user == object.getOwner()"
security: "object.getAuthor().getId() == user.getId()"
// Object properties
security: "object.isPublished() or object.getAuthor() == user"
security: "object.getStatus() == 'draft' and object.getAuthor() == user"
// Voters
security: "is_granted('EDIT', object)"
security: "is_granted('VIEW', object)"
// Combined conditions
security: "is_granted('ROLE_ADMIN') or (is_granted('ROLE_USER') and object.getAuthor() == user)"
// Request data (for POST/PUT)
security: "is_granted('ROLE_ADMIN') or request.get('category') != 'restricted'"Collection Security
Filter Collections by User
<?php
// src/Doctrine/CurrentUserExtension.php
namespace App\Doctrine;
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\Post;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\SecurityBundle\Security;
final class CurrentUserExtension implements QueryCollectionExtensionInterface
{
public function __construct(
private Security $security,
) {}
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
// Only filter Post resources
if ($resourceClass !== Post::class) {
return;
}
// Admins see everything
if ($this->security->isGranted('ROLE_ADMIN')) {
return;
}
$user = $this->security->getUser();
$alias = $queryBuilder->getRootAliases()[0];
if ($user) {
// Authenticated: see published + own drafts
$queryBuilder
->andWhere(sprintf(
'%s.isPublished = true OR %s.author = :currentUser',
$alias,
$alias
))
->setParameter('currentUser', $user);
} else {
// Anonymous: only published
$queryBuilder
->andWhere(sprintf('%s.isPublished = true', $alias));
}
}
}Filter Item Queries
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
final class CurrentUserExtension implements
QueryCollectionExtensionInterface,
QueryItemExtensionInterface
{
public function applyToItem(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
array $identifiers,
?Operation $operation = null,
array $context = []
): void {
// Same logic as collection
$this->addWhere($queryBuilder, $resourceClass);
}
public function applyToCollection(/* ... */): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
// Shared filtering logic
}
}Property-Level Security
Hide fields based on permissions:
<?php
// src/Entity/User.php
use Symfony\Component\Serializer\Attribute\Groups;
class User
{
#[Groups(['user:read', 'admin:read'])]
private ?int $id = null;
#[Groups(['user:read', 'admin:read'])]
private string $name;
// Only visible to admins and the user themselves
#[Groups(['user:owner', 'admin:read'])]
private string $email;
// Only visible to admins
#[Groups(['admin:read'])]
private array $roles;
// Never exposed
private string $password;
}With context builder for dynamic groups:
<?php
// src/Serializer/UserContextBuilder.php
final class UserContextBuilder implements SerializerContextBuilderInterface
{
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if ($this->security->isGranted('ROLE_ADMIN')) {
$context['groups'][] = 'admin:read';
}
// Check if viewing own profile
$resourceId = $request->attributes->get('id');
$currentUser = $this->security->getUser();
if ($currentUser && $currentUser->getId() == $resourceId) {
$context['groups'][] = 'user:owner';
}
return $context;
}
}JWT Authentication
# config/packages/security.yaml
security:
firewalls:
api:
pattern: ^/api
stateless: true
jwt: ~
access_control:
- { path: ^/api/login, roles: PUBLIC_ACCESS }
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }Rate Limiting
use Symfony\Component\RateLimiter\Attribute\RateLimit;
#[ApiResource(
operations: [
new Post(
security: "is_granted('ROLE_USER')",
),
],
)]
#[RateLimit(limit: 10, interval: '1 minute')]
class Comment { /* ... */ }Best Practices
1. Use voters for complex authorization logic 2. Filter collections with Doctrine extensions 3. Fail secure - deny by default 4. Clear error messages - help users understand 5. Test security - verify both grant and deny cases 6. Audit sensitive operations - log access attempts
Skill Operating Checklist
Design checklist
- Confirm operation boundaries and invariants first.
- Minimize scope while preserving contract correctness.
- Test both happy path and negative path behavior.
Validation commands
- ./vendor/bin/phpunit --filter=Api
- ./vendor/bin/phpstan analyse
- php bin/console debug:router
Failure modes to test
- Invalid payload or forbidden actor.
- Boundary values / not-found cases.
- Retry or partial-failure behavior for async flows.
Related skills
How it compares
Pick symfony:api-platform-security over generic Symfony security skills when you need API Platform operation contracts, securityPostValidation, and serialization boundary guardrails together.
FAQ
What does symfony:api-platform-security configure?
symfony:api-platform-security configures operation-level security on API Platform resources using security expressions, voters, and securityPostValidation. It keeps serialization boundaries explicit so internal entity fields are not exposed.
What workflow does symfony:api-platform-security follow?
symfony:api-platform-security first defines operation contracts and payload boundaries, then implements resource or DTO changes with explicit mapping, applies validation and security constraints, and verifies both authorized and denied requests.
Where are deeper security patterns documented?
symfony:api-platform-security points to `reference.md` and `docs/complexity-tiers.md` inside the skill for progressive disclosure when operation security needs advanced voter or tiered complexity guidance.