I am working on a small Doctrine2-backed project using Symfony2 for the first time. Currently I'm struggling with the security component of symfony2, to be exact with the authentication mechanism described in the documentation.
I want to use a form-based authentication and did everything stated in the docs:
I have an security.yml configuration file which looks like this:
security.config:
firewalls:
admin:
pattern: /admin/.*
form-login: true
logout: true
login_path: /login
check_path: /validateLogin
always_use_default_target_path: false
target_path_parameter: target
check_page:
pattern: /validateLogin
form-login: true
login_path: /login
check_path: /validateLogin
always_use_default_target_path: false
target_path_parameter: target
public:
pattern: /.*
security: false
providers:
admin:
password_encoder: md5
entity:
class: AdminBundle:User
property: username
access_control:
- { path: /admin/.*, role: ROLE_ADMIN }
- { path: /validateLogin, role: IS_AUTHENTICATED_ANONYMOUSLY }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
The check_page is excluded from the "secureless" area after reading a similar thread on devcomments.
In my routing configuration I include two rules for the authentication:
_security_login:
pattern: /login
defaults:
_controller: PublicBundle:Auth:index
_security_check:
pattern: /validateLogin
The entity class I am using to represent a user is an Doctrine2 entity and implements the AccountInterface:
<?php
namespace Application\AdminBundle\Entity;
use Symfony\Component\Security\User\AccountInterface;
/**
* @orm:Entity
*/
class User implements AccountInterface
{
/**
* @orm:Id
* @orm:Column(type="integer")
* @orm:GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @orm:Column(type="string", length="255")
*/
protected $username;
/**
* @orm:Column(type="string", length="40")
*/
protected $password;
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
/**
* Implementing the AccountInterface interface
*/
public function __toString()
{
return $this->getUsername();
}
public function getRoles()
{
return array('ROLE_ADMIN');
}
public function eraseCredentials()
{
}
public function getSalt()
{
return $this->getId();
}
}
In the class AuthController I am using the example code from the symfony2 documents:
public function indexAction()
{
if ($this->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $this->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $this->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
}
return
$this->render(
'PublicBundle:Auth:index.twig',
array(
'last_username' => $this->get('request')->getSession()->get(SecurityContext::LAST_USERNAME),
'error' => $error));
}
Now comes the problem: The redirection rule from http://symfony2.localhost/app_dev.php/admin/test to http://symfony2.localhost/app_dev.php/login works but after entering username/password and submitting the login form, I am being redirected to the login url again without an error message.
I know that this is probably a really basic issue but since there is not yet much documentation on symfony2, I think this is a good place to ask questions like this one. In general there are some points inside a symfony2 project which seem to be working magically (of course DI-backed) which make the learning process a bit hard. My thoughts on how the authentication works is that there is some magical Controller which catches the validateLogin action, looks for an entity repository for my User entity, calls findOneBy('username' => $username) and compares the passwords... is this right?
Thank you in advance for any hint, I have been googling this issue for more some hours now... :)
Paul
Symfony provides many tools to secure your application. Some HTTP-related security tools, like secure session cookies and CSRF protection are provided by default.
Certificate authentication With most firewalls you can use a public signed certificate or a self signed certificate for firewall authentication. If a firewall is public facing to anyone from the outside world it should be setup with a publicly recognisable certificate to authenticate itself to anonymous users.
Well, entity is a type of object that is used to hold data. Each instance of entity holds exactly one row of targeted database table. As for the directories, Symfony2 has some expectations where to find classes - that goes for entities as well.
The authenticate() method is the most important method of the authenticator. Its job is to extract credentials (e.g. username & password, or API tokens) from the Request object and transform these into a security Passport (security passports are explained later in this article).
My thoughts on how the authentication works is that there is some magical Controller which catches the validateLogin action, looks for an entity repository for my User entity, calls findOneBy('username' => $username) and compares the passwords... is this right?
You're wrong. Authentication doesn't involve any controller, that's why you don't specify any in _security_check
route. Auth is based on EventDispatcher
. Whenever you specify some listener in your firewall (eg. form_login
, anonymous
, logout
etc.) you actually register a new listener for core.security
event. Symfony\Component\HttpKernel\Security\Firewall::handle()
is a place where these listeners are actually registered.
The general, simplified flow:
_username
and _password
fields).core.security
event is fired.UsernamePasswordFormAuthenticationListener
is fired (handle()
method) and checks whether:
check_path
option._username
and _password
parameters.attemptAuthentication()
method).DaoAuthenticationProvider
is fired and it tries to retrieve user using Doctrine's user repository class.UsernamePasswordToken
(which contain $user
object returned by loadUserByUsername()
method) is being returned and user is redirected.Indeed security mechanism is quite complex and hard to understand (documentation isn't still finished). But when you finally understand how it works then you'll see how powerful mechanism it is.
I wrote my own authentication mechanism and it works fine.
Configuration:
I'm using custom provider and encoder.
security.config:
providers:
main:
id: project.user_repository # DI id. Doctrine's UserRepositry
check_path: /login-check
encoders:
main:
class: Project\SiteBundle\Entity\User
id: security.encoder.sha512 # DI id. Service %security.encoder.digest.class% (with "sha512" as first parameter)
firewalls:
restricted:
pattern: /panel/.*
form_login:
check_path: /login-check
public:
pattern: /.*
anonymous: true
form_login:
check_path: /login-check
logout: true
access_control:
- { path: /panel/.*, role: ROLE_USER }
- { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
As you can see /panel/*
is restricted, while /*
is public.
Service security.encoder.sha512
is a built-in encoder:
<service id="security.encoder.sha512" class="%security.encoder.digest.class%">
<argument>sha512</argument>
</service>
Project\SiteBundle\Entity\User
:
/**
* @orm:Entity(repositoryClass="Project\SiteBundle\Repository\UserRepository")
*/
class User implements AdvancedAccountInterface {
/**
* @orm:Id @orm:Column(type="integer")
* @orm:GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @orm:Column(unique=true, nullable=true)
*/
protected $email;
/**
* @orm:Column(unique=true, nullable=true)
*/
protected $xmpp;
/**
* @orm:Column(length=128)
*/
protected $password;
/**
* @orm:Column(length=16)
*/
protected $salt;
// User can be logged in using email address or xmpp adress.
// Dozens of getters/setters here.
}
Project\SiteBundle\Repository\UserRepository
class UserRepository extends EntityRepository implements UserProviderInterface {
public function loadUserByUsername($username) {
$dql = sprintf('
SELECT u
FROM %s u
WHERE u.email = :id OR u.xmpp = :id
', $this->_entityName);
$user = null;
try {
$user = $this->_em->createQuery($dql)->setParameter('id', $username)->getSingleResult();
} catch (ORMException $e) {
throw new UsernameNotFoundException("User $username not found.", $e->getCode(), $e);
}
return $user;
}
public function loadUserByAccount(AccountInterface $user) {
return $this->loadUserByUsername($user->getUsername());
}
}
Security routes and controller is same as yours.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With