Description
This issue is a continuation of #21068 and a proposal for work during the FOSSA 2 EU hackathon.
Description
Main goal: Make Tokens
first-class citizens in the security component, in favor of Tokens + Users. See https://wouterj.nl/2019/03/security-removing-user for some background.
In order to do this, I propose to first split the current tokens classes. This idea is already implemented in the Guard tokens.
Current state: all security providers have 1 token (i.e. UsernamePasswordToken
) that represents both the input as the output of the authentication. I.e. $token->getUsername()
returns either the username filled in by the user in the login form or $user->getUsername()
.
In this way, whether or not a token is authenticated is based on a boolean field (isAuthenticated
) or the existence of roles in the token.
Splitting the tokens in an AuthenticationRequestToken
and an AuthenticatedToken
imho brings the following advantages:
- It'll be possible to directly typehint for authenticated or un-authenticated tokens (no more
if (!$token->isAuthenticated()) return
). This also makes it clearer during which phase of authentication a specific class is called. - Tokens will no longer contain properties not used during that phase (i.e.
$password
should be empty after authentication) - "
$user
can be either aUserInterface
,string
or class implementing__toString()
" rule. It'll be always a string duringAuthenticationRequest
(representing the user input) and it'll always be a user inAuthenticatedToken
(representing the authenticated identity). - It removes magic parsing in
Token::__construct()
to fake JAVA constructor overloading.
Example
Before
public function getUser()
{
$token = $this->tokenStorage->getToken();
if (null === $token) return;
$user = $token->getUser();
if (!$user instanceof UserInterface) return;
return $user;
}
After
public function getUser()
{
$token = $this->tokenStorage->getToken();
if (!$token instanceof AuthenticatedTokenInterface) return;
return $token->getUser();
}
Before
interface AuthenticationManagerInterface
{
public function authenticate(TokenInterface $token): TokenInterface;
}
After
interface AuthenticationManagerInterface
{
public function authenticate(AuthenticationRequestToken $token): AuthenticatedTokenInterface;
}
How to preserve backwards compatibility?
Of course, changing such a fundemental class in the Security system is quite a challenge to do BC. I propose to following UML diagram for tokens. As far as I can see, this would be backwards compatible for the token classes.
After then, we should look at how to make all methods and typehints backwards compatible. As no return typing is used, this must not be too hard.
Proposal of the steps
- Create
AuthenticationRequestToken
andAuthenticatedToken
for anonymous and usernamepassword and deprecate the old token methods. - Update the anonymous and usernamepassword authentication providers to use these new classes.
- Update the rest of the security system to use the new classes
- Look at the rememberme tokens, they are quite special and I haven't grasped yet how they work exactly.