# Symfony Security Symfony Security is a comprehensive security system for PHP web applications that provides complete authentication, authorization, and protection mechanisms. The component enables developers to implement secure user authentication through multiple methods (HTTP basic, form login, X.509 certificates), authorize users based on roles and custom voters, encode passwords using modern algorithms, and protect against CSRF attacks. The security system is highly extensible and integrates seamlessly with the Symfony framework. The component is organized into four main subcomponents: Core (authentication, authorization, user management, password encoding), Guard (simplified custom authenticator system), Csrf (CSRF token protection), and Http (firewall and access control). These modules work together to provide a layered security architecture that handles everything from user credential validation to fine-grained access decisions on protected resources. ## Security Helper Class The `Security` class provides convenient helper methods for common security tasks including retrieving the current authenticated user and checking access permissions. It serves as the main entry point for security operations in your application. ```php security = $security; } public function index() { // Get the currently authenticated user $user = $this->security->getUser(); if ($user === null) { throw new \Exception('User not authenticated'); } // Check if user has specific role/permission if ($this->security->isGranted('ROLE_ADMIN')) { // Admin-only functionality return $this->renderAdminDashboard(); } // Check permission on specific subject $post = $this->getPost(123); if ($this->security->isGranted('edit', $post)) { // User can edit this post } // Get the authentication token directly $token = $this->security->getToken(); $username = $token->getUsername(); $roles = $token->getRoleNames(); } } ``` ## UserInterface Implementation The `UserInterface` defines the contract for user objects in the security system. Implement this interface to create custom user classes that integrate with Symfony's authentication layer. ```php username = $username; $this->password = $password; $this->roles = $roles; } public function getRoles(): array { // Guarantee every user has at least ROLE_USER $roles = $this->roles; $roles[] = 'ROLE_USER'; return array_unique($roles); } public function getPassword(): ?string { return $this->password; } public function getSalt(): ?string { // Modern encoders don't need a salt return null; } public function getUsername(): string { return $this->username; } public function eraseCredentials(): void { // Clear any temporary sensitive data // $this->plainPassword = null; } } ``` ## UserProviderInterface Implementation The `UserProviderInterface` defines how users are loaded from storage. Implement this to create custom user providers that fetch users from databases, APIs, or other sources. ```php connection = $connection; } public function loadUserByUsername($username): UserInterface { $stmt = $this->connection->prepare( 'SELECT id, username, password, roles FROM users WHERE username = :username' ); $stmt->execute(['username' => $username]); $userData = $stmt->fetch(\PDO::FETCH_ASSOC); if (!$userData) { $exception = new UsernameNotFoundException( sprintf('Username "%s" does not exist.', $username) ); $exception->setUsername($username); throw $exception; } return new AppUser( $userData['username'], $userData['password'], json_decode($userData['roles'], true) ?? ['ROLE_USER'] ); } public function refreshUser(UserInterface $user): UserInterface { if (!$user instanceof AppUser) { throw new UnsupportedUserException( sprintf('Instances of "%s" are not supported.', get_class($user)) ); } return $this->loadUserByUsername($user->getUsername()); } public function supportsClass($class): bool { return AppUser::class === $class || is_subclass_of($class, AppUser::class); } } ``` ## InMemoryUserProvider The `InMemoryUserProvider` provides a simple way to configure users directly in code, useful for testing, prototyping, or simple applications with fixed user sets. ```php [ 'password' => '$2y$13$encodedPasswordHash', 'roles' => ['ROLE_ADMIN', 'ROLE_USER'], 'enabled' => true, ], 'user' => [ 'password' => '$2y$13$anotherEncodedHash', 'roles' => ['ROLE_USER'], ], ]); // Or create users programmatically $newUser = new User( 'john_doe', // username '$2y$13$encodedHash', // encoded password ['ROLE_USER', 'ROLE_EDITOR'], // roles true, // enabled true, // accountNonExpired true, // credentialsNonExpired true // accountNonLocked ); $userProvider->createUser($newUser); // Load a user $user = $userProvider->loadUserByUsername('admin'); echo $user->getUsername(); // admin print_r($user->getRoles()); // ['ROLE_ADMIN', 'ROLE_USER'] ``` ## Password Encoding with NativePasswordEncoder The `NativePasswordEncoder` uses PHP's `password_hash()` function to securely encode passwords with modern algorithms like bcrypt and Argon2. ```php encodePassword($plainPassword, null); // Output: $argon2id$v=19$m=65536,t=4,p=1$... // Verify a password $isValid = $encoder->isPasswordValid($encodedPassword, $plainPassword, null); // Returns: true $isValid = $encoder->isPasswordValid($encodedPassword, 'wrong_password', null); // Returns: false // Check if password needs rehashing (e.g., algorithm changed) if ($encoder->needsRehash($encodedPassword)) { $newEncodedPassword = $encoder->encodePassword($plainPassword, null); // Save $newEncodedPassword to database } ``` ## UserPasswordEncoder The `UserPasswordEncoder` provides a higher-level API for encoding passwords tied to specific user classes, automatically selecting the appropriate encoder based on the user type. ```php new NativePasswordEncoder(null, null, 13), AdminUser::class => new NativePasswordEncoder(null, null, 15), // Higher cost for admins ]); $userPasswordEncoder = new UserPasswordEncoder($encoderFactory); // Encode password for a user $user = new AppUser('john', '', ['ROLE_USER']); $encodedPassword = $userPasswordEncoder->encodePassword($user, 'plain_password'); // Verify password $isValid = $userPasswordEncoder->isPasswordValid($user, 'plain_password'); // Returns: true // Check if rehashing is needed if ($userPasswordEncoder->needsRehash($user)) { $newHash = $userPasswordEncoder->encodePassword($user, $currentPlainPassword); $user->setPassword($newHash); // Persist user } ``` ## UsernamePasswordToken The `UsernamePasswordToken` represents an authentication token containing username and password credentials. It's used throughout the authentication process to carry user information. ```php getUsername(); // john_doe echo $token->getCredentials(); // plain_password echo $token->getProviderKey(); // main echo $token->isAuthenticated(); // false // Create authenticated token (after successful authentication) $authenticatedToken = new UsernamePasswordToken( $user, // UserInterface instance null, // credentials (null after auth) 'main', // provider key ['ROLE_USER', 'ROLE_ADMIN'] // roles ); echo $authenticatedToken->isAuthenticated(); // true print_r($authenticatedToken->getRoleNames()); // ['ROLE_USER', 'ROLE_ADMIN'] // Erase sensitive data after authentication $authenticatedToken->eraseCredentials(); echo $authenticatedToken->getCredentials(); // null ``` ## TokenStorage The `TokenStorage` holds the current user's authentication token and provides access to it throughout the request lifecycle. ```php setToken($token); // Retrieve current token $currentToken = $tokenStorage->getToken(); if ($currentToken !== null) { $user = $currentToken->getUser(); $username = $currentToken->getUsername(); $isAuthenticated = $currentToken->isAuthenticated(); } // Clear authentication (logout) $tokenStorage->setToken(null); // Reset storage (useful in long-running processes) $tokenStorage->reset(); // Set lazy initializer for deferred token loading $tokenStorage->setInitializer(function () use ($tokenStorage, $sessionHandler) { // Load token from session only when needed $token = $sessionHandler->loadToken(); $tokenStorage->setToken($token); }); ``` ## AuthorizationChecker The `AuthorizationChecker` is the main entry point for authorization decisions, checking if the current user has the required permissions. ```php isGranted('ROLE_ADMIN')) { // User has admin role } // Check access on a specific subject $post = getPost(123); if ($authorizationChecker->isGranted('edit', $post)) { // User can edit this post } // Check multiple attributes (deprecated, use expression instead) if ($authorizationChecker->isGranted('ROLE_USER')) { if ($authorizationChecker->isGranted('ROLE_MODERATOR')) { // User has both roles } } ``` ## AccessDecisionManager The `AccessDecisionManager` coordinates voter decisions using configurable strategies (affirmative, consensus, unanimous) to determine access control outcomes. ```php getToken(); $granted = $manager->decide($token, ['ROLE_ADMIN'], null); // Decision with subject $granted = $manager->decide($token, ['edit'], $post); ``` ## Custom Voter Implementation The `Voter` abstract class simplifies creating custom voters for fine-grained authorization logic on specific subjects. ```php getUser(); // Anonymous users can only view published posts if (!$user instanceof UserInterface) { return $attribute === self::VIEW && $subject->isPublished(); } /** @var Post $post */ $post = $subject; switch ($attribute) { case self::VIEW: return $this->canView($post, $user); case self::EDIT: return $this->canEdit($post, $user); case self::DELETE: return $this->canDelete($post, $user); } return false; } private function canView(Post $post, UserInterface $user): bool { // Published posts visible to all authenticated users if ($post->isPublished()) { return true; } // Drafts only visible to owner return $user === $post->getOwner(); } private function canEdit(Post $post, UserInterface $user): bool { // Only owner can edit return $user === $post->getOwner(); } private function canDelete(Post $post, UserInterface $user): bool { // Admins and owners can delete return in_array('ROLE_ADMIN', $user->getRoles()) || $user === $post->getOwner(); } } // Register voter in AccessDecisionManager $manager = new AccessDecisionManager([ new RoleVoter(), new PostVoter(), ]); // Usage $granted = $authChecker->isGranted('edit', $post); ``` ## RoleHierarchy The `RoleHierarchy` allows defining role inheritance, where higher roles automatically include permissions of lower roles. ```php ['ROLE_MODERATOR', 'ROLE_ALLOWED_TO_SWITCH'], 'ROLE_MODERATOR' => ['ROLE_USER'], 'ROLE_SUPER_ADMIN' => ['ROLE_ADMIN', 'ROLE_DEVELOPER'], ]); // Get all reachable roles for a given role $roles = $hierarchy->getReachableRoleNames(['ROLE_ADMIN']); // Returns: ['ROLE_ADMIN', 'ROLE_MODERATOR', 'ROLE_ALLOWED_TO_SWITCH', 'ROLE_USER'] $roles = $hierarchy->getReachableRoleNames(['ROLE_SUPER_ADMIN']); // Returns: ['ROLE_SUPER_ADMIN', 'ROLE_ADMIN', 'ROLE_DEVELOPER', 'ROLE_MODERATOR', // 'ROLE_ALLOWED_TO_SWITCH', 'ROLE_USER'] // Use with RoleHierarchyVoter for authorization use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; $voter = new RoleHierarchyVoter($hierarchy); $manager = new AccessDecisionManager([$voter]); // User with ROLE_ADMIN will be granted ROLE_USER access $token = new UsernamePasswordToken($user, null, 'main', ['ROLE_ADMIN']); $granted = $manager->decide($token, ['ROLE_USER'], null); // true ``` ## CsrfTokenManager The `CsrfTokenManager` generates and validates CSRF tokens to protect forms against cross-site request forgery attacks. ```php getToken('authenticate'); $tokenValue = $token->getValue(); // In your form template: // // Validate submitted token $submittedToken = $_POST['_csrf_token'] ?? ''; $csrfToken = new CsrfToken('authenticate', $submittedToken); if (!$csrfManager->isTokenValid($csrfToken)) { throw new \RuntimeException('Invalid CSRF token'); } // Refresh token (generates new value) $newToken = $csrfManager->refreshToken('authenticate'); // Remove token (e.g., after successful form submission) $csrfManager->removeToken('authenticate'); // Different token IDs for different forms $loginToken = $csrfManager->getToken('login_form'); $deleteToken = $csrfManager->getToken('delete_item'); $settingsToken = $csrfManager->getToken('user_settings'); ``` ## Guard Authenticator Interface The Guard component provides a simplified way to create custom authentication systems. Implement `AuthenticatorInterface` to control the entire authentication process. ```php headers->has('X-API-TOKEN'); } public function getCredentials(Request $request) { return [ 'api_key' => $request->headers->get('X-API-TOKEN'), ]; } public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface { $apiKey = $credentials['api_key']; if (null === $apiKey) { return null; } // Load user by API key from your database return $this->userRepository->findByApiKey($apiKey); } public function checkCredentials($credentials, UserInterface $user): bool { // API key authentication doesn't need additional credential check // The key itself is the credential, validated in getUser() return true; } public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response { // Return null to continue the request return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response { return new JsonResponse([ 'error' => 'Authentication failed', 'message' => $exception->getMessage(), ], Response::HTTP_UNAUTHORIZED); } public function start(Request $request, AuthenticationException $authException = null): Response { return new JsonResponse([ 'error' => 'Authentication required', 'message' => 'Please provide a valid API token via X-API-TOKEN header', ], Response::HTTP_UNAUTHORIZED); } public function supportsRememberMe(): bool { return false; } } ``` ## AbstractFormLoginAuthenticator The `AbstractFormLoginAuthenticator` provides a base class for creating form-based login authenticators with common functionality pre-implemented. ```php passwordEncoder = $passwordEncoder; $this->csrfTokenManager = $csrfTokenManager; } public function supports(Request $request): bool { return $request->getPathInfo() === '/login' && $request->isMethod('POST'); } public function getCredentials(Request $request) { return [ 'username' => $request->request->get('_username'), 'password' => $request->request->get('_password'), 'csrf_token' => $request->request->get('_csrf_token'), ]; } public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface { // Validate CSRF token $token = new CsrfToken('authenticate', $credentials['csrf_token']); if (!$this->csrfTokenManager->isTokenValid($token)) { throw new InvalidCsrfTokenException(); } return $userProvider->loadUserByUsername($credentials['username']); } public function checkCredentials($credentials, UserInterface $user): bool { return $this->passwordEncoder->isPasswordValid($user, $credentials['password']); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse { // Redirect to intended page or default $targetPath = $request->getSession()->get('_security.'.$providerKey.'.target_path'); return new RedirectResponse($targetPath ?? '/dashboard'); } protected function getLoginUrl(): string { return '/login'; } } ``` ## GuardAuthenticatorHandler The `GuardAuthenticatorHandler` manages the authentication flow, handling token storage and dispatching authentication events. ```php find(123); $request = Request::createFromGlobals(); $authenticator = new LoginFormAuthenticator(/* ... */); // Authenticate user and handle success response $response = $handler->authenticateUserAndHandleSuccess( $user, $request, $authenticator, 'main' // firewall name ); // Or authenticate with existing token $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles()); $handler->authenticateWithToken($token, $request, 'main'); // Handle authentication failure $exception = new AuthenticationException('Invalid credentials'); $response = $handler->handleAuthenticationFailure($exception, $request, $authenticator, 'main'); ``` ## Summary Symfony Security is ideal for building secure web applications that require user authentication, role-based access control, and protection against common security vulnerabilities. Common use cases include: implementing login systems with multiple authentication methods (form login, API tokens, OAuth), protecting routes based on user roles, creating fine-grained access control using custom voters, securing forms against CSRF attacks, and implementing remember-me functionality. The component excels in scenarios requiring complex authorization rules, such as content management systems where users can only edit their own content, or multi-tenant applications with organization-level permissions. Integration with Symfony applications is seamless through the SecurityBundle, but the component can also be used standalone in any PHP application. The Guard component particularly simplifies custom authentication by providing a single interface to implement the entire authentication flow. For best practices, always use the password encoder with bcrypt or Argon2 algorithms, implement CSRF protection on all state-changing forms, use voters for object-level permissions, and configure role hierarchies to avoid redundant role checks. The component's event system allows extending the security layer with custom listeners for audit logging, multi-factor authentication, or account lockout policies.