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:

  1. Generate new hash:

    php bin/console security:hash-password "NewPassword"
    
  2. Update ADMIN_PASSWORD in .env (or .env.local for production)

  3. Clear cache:

    php bin/console cache:clear
    

Troubleshooting

Authentication Prompt Loops

Symptom: Browser keeps showing login prompt even with correct credentials.

Causes:

  1. Apache not passing Authorization header ? Check CGIPassAuth On is in apache config
  2. Password hash mismatch ? Verify hash format and hasher configuration
  3. 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.