Belajar Symfony - Security: Authentication & Authorization
Episode 12 of 27

Belajar Symfony - Security: Authentication & Authorization

Mengamankan aplikasi dengan SecurityBundle: login form dengan authenticator, password hashing yang aman, roles & access_control untuk RBAC, API token untuk stateless auth, serta custom voters untuk otorisasi granular berbasis objek.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Sejak episode 3 aplikasi kita terbuka tanpa identitas. Episode 12 menutup celah itu: authentication (siapa kamu) dan authorization (boleh apa). Ini salah satu alasan utama Symfony dipilih di enterprise — sistem keamanannya matang, terstruktur, dan bisa diverifikasi.

Mengapa dipisahkan authn dan authz? Karena keduanya jawaban atas pertanyaan berbeda: authn membuktikan identitas (login, token), authz memutuskan akses (role, owner). Symfony memisahkan keduanya dengan tegas — dan kebiasaan ini membuat kebijakan akses bisa diaudit di satu tempat (security.yaml + voters), bukan tersebar di logika aplikasi.

Menginstall Security dan Membuat User

Install SecurityBundle
composer require security-bundle

Lalu buat entity User dan sistem login:

Buat User dan login form
php bin/console make:user
php bin/console make:auth

make:user menghasilkan entity User dengan properti email (identifier), roles, dan password. make:auth menghasilkan Authenticator (LoginFormAuthenticator) yang menangani form login, plus route login/logout.

Login Form dan Autentikasi

Konfigurasi firewall di security.yaml menentukan bagaimana request diautentikasi:

config/packages/security.yaml
security:
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
 
    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
 
    firewalls:
        main:
            lazy: true
            provider: app_user_provider
            form_login:
                login_path: app_login
                check_path: app_login
            logout:
                path: app_logout
 
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/login, roles: PUBLIC_ACCESS }
        - { path: ^/, roles: PUBLIC_ACCESS }

access_control dievaluasi berurutan dari atas — pola ^/admin ditulis sebelum ^/ agar tidak tertutup rule umum. PUBLIC_ACCESS berarti bebas tanpa login.

Password Hashing yang Benar

Jangan pernah menyimpan password plaintext. Symfony memakai password_hashers dengan algoritma auto — yang memilih algoritma terbaik (bcrypt/argon2id):

Hash password saat register
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
 
public function register(
    Request $request,
    UserPasswordHasherInterface $hasher,
    EntityManagerInterface $em,
): Response
{
    $user = new User();
    $user->setEmail($request->request->get('email'));
    $user->setPassword($hasher->hashPassword($user, $request->request->get('password')));
 
    $em->persist($user);
    $em->flush();
 
    return $this->redirectToRoute('app_home');
}

hashPassword() selalu menambahkan salt acak per user dan memakai work factor yang cukup. Verify dilakukan otomatis oleh form_login; untuk konteks lain, ada UserPasswordHasherInterface::isPasswordValid().

Roles dan RBAC

ROLE_USER diberikan otomatis saat login; ROLE_ADMIN perlu di-assign secara eksplisit. Model hierarki memudahkan RBAC:

Hierarki role
security:
    role_hierarchy:
        ROLE_ADMIN: ROLE_USER
        ROLE_EDITOR: ROLE_USER

Pengecekan role di kode:

Cek role di controller
// method-based (recommended)
public function dashboard(): Response
{
    $this->denyAccessUnlessGranted('ROLE_ADMIN');
 
    return $this->render('admin/dashboard.html.twig');
}
Cek role di template
{% if is_granted('ROLE_ADMIN') %}
    <a href="{{ path('app_admin_dashboard') }}">Panel Admin</a>
{% endif %}

Aturan emas: grant access via role, bukan via email tertentu. Kebijakan berbasis role bisa diubah di satu tempat tanpa menyentuh kode.

API Token: Auth untuk Stateless

Untuk API/SPA, autentikasi token lebih cocok daripada session. Tambahkan access token pada user dan authenticator token sederhana. Setelah make:user dengan opsi api-token, verifikasi berjalan seperti:

Mencoba API dengan token
curl -H "Authorization: Bearer TOKEN" https://localhost:8000/api/me

Firewall API ditambahkan terpisah agar route web dan API tidak tercampur:

Firewall API dengan token
firewalls:
    api:
        pattern: ^/api
        stateless: true
        custom_authenticator: App\Security\ApiTokenAuthenticator

Detail lanjutan — JWT, refresh token, OAuth2 — kita bedah penuh di episode 19.

Voters: Otorisasi Granular

Role hanya menjawab "grup apa". Untuk kebijakan seperti "hanya penulis artikel yang bisa mengedit artikelnya", gunakan Voter:

Buat voter
php bin/console make:voter ArticleVoter
src/Security/ArticleVoter.php
use App\Entity\Article;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
 
final class ArticleVoter extends Voter
{
    public const EDIT = 'EDIT';
 
    protected function supports(string $attribute, mixed $subject): bool
    {
        return $attribute === self::EDIT && $subject instanceof Article;
    }
 
    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        if (!$user instanceof \App\Entity\User) {
            return false;
        }
 
        return $subject->getAuthor() === $user
            || in_array('ROLE_ADMIN', $user->getRoles(), true);
    }
}

Dipakai di controller:

Voter di controller
public function edit(Article $article): Response
{
    $this->denyAccessUnlessGranted(ArticleVoter::EDIT, $article);
 
    // hanya penulis atau admin sampai di sini...
}

Voter dipanggil otomatis oleh is_granted/denyAccessUnlessGranted; kombinasi supports() + voteOnAttribute() membuatnya mudah di-test dan ditambah.

Warning

Jangan pernah menggabungkan otorisasi dan data yang bisa diprediksi: mengembalikan 404 untuk resource yang bukan milik user (misal if ($article->getAuthor() !== $user) throw $this->createNotFoundException()) adalah information disclosure yang halus. Lebih baik gunakan voter + response 403 yang jujur.

Common Pitfalls

  • Rule access_control tertukar urutan — rule spesifik (^/admin) harus dulu dari yang umum (^/).
  • Password di-set langsung — selalu hashPassword(), jangan setPassword($plain).
  • Login check path salahlogin_path adalah halaman form, check_path adalah target submit; keduanya sering diset nama route yang sama.

Penutup

Pada episode 12 ini, kalian telah membangun autentikasi dan otorisasi.

Inti yang harus dibawa pulang:

  • Authn (login) dan Authz (akses) dipisah: firewall vs roles/voters.
  • make:user + make:auth menghasilkan user entity dan authenticator.
  • Selalu hash password via UserPasswordHasherInterface; access_control dievaluasi berurutan.
  • RBAC via role_hierarchy; Voter untuk kebijakan berbasis objek.
  • Token auth untuk stateless API; JWT/OAuth2 di episode 19.

Di episode 13 selanjutnya kita membangun API di atas Symfony: API Platform & REST API — resource & attributes, filters & pagination, OpenAPI/Swagger otomatis, JSON-LD/HAL, dan praktik CRUD API lengkap. Sampai jumpa di episode 13!

Belajar Symfony - Security: Authentication & Authorization | Belajar Symfony