This guide covers security features and best practices in ReSymf-CMS.
Passwords are hashed with bcrypt (cost 12):
# config/packages/security.yaml
security:
password_hashers:
App\Entity\User:
algorithm: bcrypt
cost: 12Brute force protection: 5 attempts per minute.
firewalls:
main:
login_throttling:
max_attempts: 5
interval: '1 minute'After 5 failed attempts, the user must wait 1 minute.
Optional "remember me" functionality (1 week):
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week
path: /
always_remember_me: falseReSymf-CMS supports TOTP-based 2FA via scheb/2fa-bundle.
# config/packages/scheb_2fa.yaml
scheb_two_factor:
security_tokens:
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
totp:
enabled: true
server_name: ReSymf-CMS
issuer: ReSymf-CMS
window: 1
parameters:
image: 'https://your-domain.com/logo.png'# config/routes/scheb_2fa.yaml
2fa_login:
path: /2fa
defaults:
_controller: "scheb_two_factor.form_controller::form"
2fa_login_check:
path: /2fa_checkUsers with 2FA enabled have:
totpSecret- The shared secretisTotpEnabled()- Whether 2FA is active
- User enables 2FA in settings
- System generates TOTP secret
- User scans QR code with authenticator app
- User confirms with a valid code
- 2FA is now required at login
All forms and destructive actions require CSRF tokens.
form_login:
enable_csrf: trueIn controller:
if ($this->isCsrfTokenValid('delete' . $entity->getId(), $request->request->get('_token'))) {
// Process action
}In Twig:
<form method="post">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ entity.id) }}">
<button type="submit">Delete</button>
</form>role_hierarchy:
ROLE_ADMIN: ROLE_USERaccess_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/api, roles: ROLE_USER }#[IsGranted('ROLE_ADMIN')]
class AdminController extends AbstractControllerAdmins can impersonate users for debugging:
switch_user: trueUsage:
https://example.com?_switch_user=target_username
Exit impersonation:
https://example.com?_switch_user=_exit
Recommended Nginx headers:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;use Symfony\Component\Validator\Constraints as Assert;
#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 100)]
private string $name;
#[Assert\Email]
private string $email;
#[Assert\Regex(pattern: '/^[a-z0-9-]+$/')]
private string $slug;Forms automatically validate against entity constraints.
Always use parameterized queries:
// Good - parameterized
$this->createQueryBuilder('u')
->andWhere('u.username = :username')
->setParameter('username', $username);
// Bad - string concatenation
// $query = "SELECT * FROM users WHERE username = '$username'";Twig automatically escapes output:
{{ user.name }} {# Auto-escaped #}
{{ user.html|raw }} {# Use raw only when necessary #}Never commit secrets. Use .env.local:
APP_SECRET=your-secret-key
DATABASE_URL=mysql://user:password@localhost/db.env.local
.env.*.local
*.pem
*.keycomposer auditThe security-scan.yml workflow runs weekly:
- Composer vulnerability check
- OWASP dependency check
- Psalm taint analysis
Security best practices:
- Strong APP_SECRET (32+ random bytes)
- Database credentials not in code
- CSRF tokens on all forms
- Input validation on all user input
- Parameterized database queries
- Rate limiting on login
- 2FA available for sensitive accounts
- Regular security updates
- HTTPS in production
- Proper file permissions