Symfony EasyAdmin with HTTP Basic Auth Setup
EasyAdmin with HTTP Basic Auth Setup
This guide explains how to set up EasyAdmin with HTTP Basic Authentication for a simple admin backend.
Installation
1. Install EasyAdmin Bundle
composer require easycorp/easyadmin-bundle
Security Configuration
2. Configure Apache to Pass Authorization Header
HTTP Basic Auth requires the Authorization header to reach PHP. Apache with proxy_fcgi strips this header by default.
Edit .docker/apache/apache2.conf and add CGIPassAuth On to the VirtualHost Directory section:
<Directory /var/www/app/public>
AllowOverride None
Require all granted
FallbackResource /index.php
# Pass HTTP Authorization header to PHP-FPM (required for HTTP Basic Auth)
CGIPassAuth On
</Directory>
Restart Apache
3. Generate Password Hash
Generate a bcrypt hash for your admin password:
php bin/console security:hash-password "YourSecurePassword"
Copy the hash from the output (looks like $2y$13$...).
4. Configure Environment Variable
Add the password hash to .env:
# EasyAdmin HTTP Basic Auth ADMIN_PASSWORD='$2y$13$p/iHLcHZEbXqDwrLWzPs5umeKtMxe/vHEwIGuYYTXAVdFAvRYW4s2'
Important: Wrap the hash in single quotes to prevent shell interpretation of $ characters.
For production, add this to .env.local (which is gitignored) instead of .env.
5. Configure Security
Edit config/packages/security.yaml:
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
Symfony\Component\Security\Core\User\InMemoryUser: 'auto'
providers:
users_in_memory:
memory:
users:
admin:
password: '%env(ADMIN_PASSWORD)%'
roles: ['ROLE_ADMIN']
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin
stateless: true
provider: users_in_memory
http_basic:
realm: Admin Area
main:
lazy: true
provider: users_in_memory
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
Key points:
- InMemoryUser hasher is set to auto (bcrypt)
- Password is loaded from ADMIN_PASSWORD env var
- stateless: true prevents session issues with HTTP Basic Auth
- Access control requires ROLE_ADMIN for /admin path
Create Admin Controllers
6. Create Dashboard Controller
Create src/Controller/Admin/DashboardController.php:
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Booking;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Config\UserMenu;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\User\UserInterface;
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('Admin');
}
public function configureUserMenu(UserInterface $user): UserMenu
{
// Disable logout menu item (HTTP Basic Auth has no logout)
return parent::configureUserMenu($user)
->displayUserName()
->setMenuItems([]);
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::linkToCrud('Users', 'fa fa-users', User::class);
yield MenuItem::linkToCrud('Bookings', 'fa fa-calendar', Booking::class);
}
}
Note: configureUserMenu() disables the logout link since HTTP Basic Auth has no logout mechanism.
7. Create CRUD Controllers
Create CRUD controllers for your entities. Example for User:
php bin/console make:admin:crud # Select your entity when prompted
Or create manually in src/Controller/Admin/UserCrudController.php:
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('User')
->setEntityLabelInPlural('Users')
->setDefaultSort(['id' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->onlyOnIndex();
yield TextField::new('firstName', 'First Name');
yield TextField::new('lastName', 'Last Name');
yield EmailField::new('email', 'Email');
// Add more fields as needed
}
}
8. Create Dashboard Template
Create templates/admin/dashboard.html.twig:
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block page_title %}
<h1>Admin Dashboard</h1>
{% endblock %}
{% block page_content %}
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3>Welcome to Admin</h3>
</div>
<div class="card-body">
<p>Use the menu on the left to manage:</p>
<ul>
<li><strong>Users</strong> - Manage tenants and customers</li>
<li><strong>Bookings</strong> - View and manage all bookings</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
Testing
9. Clear Cache and Test
php bin/console cache:clear
Navigate to http://localhost:8020/admin in your browser. You should see a browser login prompt.
Credentials:
- Username: admin
- Password: (the password you hashed in step 3)
Test with curl:
curl -u admin:YourSecurePassword http://localhost:8020/admin
Should return HTTP 200 with HTML content.
Changing the Password
To change the admin password:
-
Generate new hash:
php bin/console security:hash-password "NewPassword"
-
Update ADMIN_PASSWORD in .env (or .env.local for production)
-
Clear cache:
php bin/console cache:clear
Troubleshooting
Authentication Prompt Loops
Symptom: Browser keeps showing login prompt even with correct credentials.
Causes:
- Apache not passing Authorization header ? Check CGIPassAuth On is in apache config
- Password hash mismatch ? Verify hash format and hasher configuration
- Browser cached failed attempt ? Try incognito/private window
Debug:
# Check if Authorization header reaches PHP curl -v -u admin:password http://localhost:8020/admin 2>&1 | grep Authorization # Check resolved password hash php bin/console debug:dotenv | grep ADMIN_PASSWORD # Check security config php bin/console debug:config security providers
401 Unauthorized
Symptom: HTTP 401 with correct credentials.
Check logs:
tail -20 var/log/dev.log | grep security
Look for "The presented password is invalid" - indicates password hash mismatch.
500 Internal Server Error
Symptom: Authentication succeeds but page shows 500 error.
Common cause: Missing logout configuration. Ensure:
- Firewall has stateless: true
- configureUserMenu() disables logout link
Security Considerations
For Development
- HTTP Basic Auth with single admin user is acceptable
- Store password in .env (committed) with a simple password like "admin"
For Production
- Move ADMIN_PASSWORD to .env.local (gitignored)
- Use a strong password (20+ characters, mixed case, numbers, symbols)
- Consider upgrading to form login with database-backed users
- Add IP whitelisting in addition to password protection
- Use HTTPS (Basic Auth sends credentials in base64, not encrypted)
Limitations of HTTP Basic Auth
- No logout mechanism (close all browser windows to "logout")
- Browser caches credentials
- Credentials sent with every request
- No password reset flow
- No user management UI
For production systems with multiple admins, consider using Symfony's form login with database users instead.

