Created
May 31, 2018 06:32
-
-
Save jonathangreco/b21dd8280928028828aa438a45fdb65a to your computer and use it in GitHub Desktop.
Authentication issue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types = 1); | |
namespace App\Api\Auth\Provider; | |
use App\Api\User\Entity\User; | |
use App\Api\User\Repository\UserRepository; | |
use App\Domain\User\ValueObject\Email; | |
use Symfony\Component\Security\Core\Exception\UnsupportedUserException; | |
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
use Symfony\Component\Security\Core\User\UserProviderInterface; | |
class AuthProvider implements UserProviderInterface | |
{ | |
/** | |
* @var \App\Api\User\Repository\UserRepository | |
*/ | |
private $userRepository; | |
/** | |
* AuthProvider constructor. | |
* @param \App\Api\User\Repository\UserRepository $repository | |
*/ | |
public function __construct(UserRepository $repository) | |
{ | |
$this->userRepository = $repository; | |
} | |
/** | |
* @param string $email | |
* @return mixed | |
*/ | |
public function loadUserByUsername($email) | |
{ | |
try { | |
$user = $this->userRepository->getUser($email); | |
} catch (UnsupportedUserException $e) { | |
throw new UsernameNotFoundException('User not found', 1001, $e); | |
} | |
return $user; | |
} | |
/** | |
* @param \Symfony\Component\Security\Core\User\UserInterface | User $user | |
* @return mixed | |
*/ | |
public function refreshUser(UserInterface $user) | |
{ | |
return $this->loadUserByUsername($user->getEmail()); | |
} | |
/** | |
* Qualify the supported class for this provider | |
* @param string $class | |
* @return string | |
*/ | |
public function supportsClass($class) | |
{ | |
if (!$class instanceof User) { | |
throw new UnsupportedUserException( | |
sprintf('Entity given is not supported, expected User got %s', $class), | |
1000 | |
); | |
} | |
return $class; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types = 1); | |
namespace App\Api\Auth\Guard; | |
use App\Api\User\Repository\UserRepository; | |
use App\Domain\User\Exception\InvalidCredentialsException; | |
use App\Domain\User\ValueObject\Credentials; | |
use App\Domain\User\ValueObject\Email; | |
use App\Domain\User\ValueObject\HashedPassword; | |
use Symfony\Component\HttpFoundation\RedirectResponse; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Core\Exception\AuthenticationException; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
use Symfony\Component\Security\Core\User\UserProviderInterface; | |
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; | |
/** | |
* Allow the authentication by giving credential, when login process achieved and valid, profile page show up | |
* Class LoginAuthenticator | |
* @package App\Api\Auth\Guard | |
*/ | |
final class LoginAuthenticator extends AbstractFormLoginAuthenticator | |
{ | |
const LOGIN = 'login'; | |
const SUCCESS_REDIRECT = 'profile'; | |
/** | |
* @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface | |
*/ | |
private $router; | |
/** | |
* @var \App\Api\User\Repository\UserRepository | |
*/ | |
private $repository; | |
public function __construct(UrlGeneratorInterface $router, UserRepository $userRepository) | |
{ | |
$this->router = $router; | |
$this->repository = $userRepository; | |
} | |
/** | |
* This method will pass the returning array to getUser and getCredential methods automatically | |
* @param \Symfony\Component\HttpFoundation\Request $request | |
* @return array | |
*/ | |
public function getCredentials(Request $request) | |
{ | |
return [ | |
'email' => $request->get('email'), | |
'password' => $request->get('password') | |
]; | |
} | |
/** | |
* In the case or the Guard and the Authenticator is the same, this method is called just after getCredentials | |
* @param mixed $credentials | |
* @param \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider | |
* @return null|\Symfony\Component\Security\Core\User\UserInterface|void | |
*/ | |
public function getUser($credentials, UserProviderInterface $userProvider): UserInterface | |
{ | |
try { | |
$email = $credentials['email']; | |
$mail = Email::fromString($email); | |
$user = $userProvider->loadUserByUsername($mail->toString()); | |
if ($user instanceof UserInterface) { | |
$this->checkCredentials($credentials, $user); | |
} | |
} catch (InvalidCredentialsException $exception) { | |
throw new AuthenticationException(); | |
} | |
return $user; | |
} | |
/** | |
* The ùail has been found, because a user has been identified, we take the has password we have to compare | |
* @param mixed $credentials | |
* @param \Symfony\Component\Security\Core\User\UserInterface $user | |
* @return bool | |
*/ | |
public function checkCredentials($credentials, UserInterface $user) | |
{ | |
$mail = Email::fromString($credentials['email']); | |
$userCredentials = new Credentials($mail, HashedPassword::fromHash($user->getPassword())); | |
// Plain password compared | |
$match = $userCredentials->password->match($credentials['password']); | |
if (!$match) { | |
throw new InvalidCredentialsException(); | |
} | |
return true; | |
} | |
/** | |
* Called when authentication executed and was successful! | |
* | |
* This should return the Response sent back to the user, like a | |
* RedirectResponse to the last page they visited. | |
* | |
* If you return null, the current request will continue, and the user | |
* will be authenticated. This makes sense, for example, with an API. | |
* | |
* @param \Symfony\Component\HttpFoundation\Request $request | |
* @param \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token | |
* @param string $providerKey | |
* | |
* @return RedirectResponse | |
*/ | |
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) | |
{ | |
return new RedirectResponse($this->router->generate(self::SUCCESS_REDIRECT)); | |
} | |
protected function getLoginUrl(): string | |
{ | |
return $this->router->generate(self::LOGIN); | |
} | |
/** | |
* Does the authenticator support the given Request? | |
* | |
* If this returns false, the authenticator will be skipped. | |
* | |
* @param Request $request | |
* | |
* @return bool | |
*/ | |
public function supports(Request $request) | |
{ | |
return $request->getPathInfo() === $this->router->generate(self::LOGIN) && $request->isMethod('POST'); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
security: | |
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers | |
providers: | |
users: | |
id: 'App\Api\Auth\Provider\AuthProvider' | |
firewalls: | |
dev: | |
pattern: ^/(_(profiler|wdt)|css|images|js)/ | |
security: false | |
login: | |
stateless: true | |
anonymous: true | |
provider: users | |
guard: | |
entry_point: 'App\User\Auth\Guard\LoginAuthenticator' | |
authenticators: | |
- 'App\Api\Auth\Guard\LoginAuthenticator' | |
form_login: | |
login_path: /sign-in | |
check_path: sign-in | |
logout: | |
path: /logout | |
target: / | |
api: | |
pattern: ^/(/user/*|/api|) | |
stateless: true | |
guard: | |
authenticators: | |
- 'App\Api\Auth\Guard\LoginAuthenticator' | |
# Easy way to control access for large sections of your site | |
# Note: Only the *first* access control that matches will be used | |
access_control: | |
- { path: ^/api, roles: USER } | |
- { path: ^/user/*, roles: USER } | |
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types = 1); | |
namespace App\Api\User\Entity; | |
use App\Domain\User\Repository\Interfaces\CRUDInterface; | |
use App\Shared\Entity\Traits\CreatedTrait; | |
use App\Shared\Entity\Traits\DeletedTrait; | |
use App\Shared\Entity\Traits\EntityNSTrait; | |
use App\Shared\Entity\Traits\IdTrait; | |
use Doctrine\ORM\Mapping as ORM; | |
use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
/** | |
* @ORM\Table(name="app_users") | |
* @ORM\Entity(repositoryClass="App\Api\User\Repository\UserRepository") | |
*/ | |
class User implements UserInterface, CRUDInterface, \Serializable, EncoderAwareInterface | |
{ | |
use IdTrait; | |
use CreatedTrait; | |
use DeletedTrait; | |
use EntityNSTrait; | |
/** | |
* @ORM\Column(type="string", length=25, unique=false, nullable=true) | |
*/ | |
private $username; | |
/** | |
* @ORM\Column(type="string", length=64) | |
*/ | |
private $password; | |
/** | |
* @ORM\Column(type="string", length=254, unique=true) | |
*/ | |
private $email; | |
/** | |
* @return mixed | |
*/ | |
public function getEmail() | |
{ | |
return $this->email; | |
} | |
/** | |
* @param mixed $email | |
* @return User | |
*/ | |
public function setEmail($email) | |
{ | |
$this->email = $email; | |
return $this; | |
} | |
public function __construct() | |
{ | |
} | |
public function getUsername() | |
{ | |
return $this->username; | |
} | |
public function getSalt() | |
{ | |
// you *may* need a real salt depending on your encoder | |
// see section on salt below | |
return null; | |
} | |
public function getPassword() | |
{ | |
return $this->password; | |
} | |
public function getRoles() | |
{ | |
return array('USER'); | |
} | |
/** | |
* From UserInterface | |
*/ | |
public function eraseCredentials() | |
{ | |
// Never used ?‡ | |
} | |
/** @see \Serializable::serialize() */ | |
public function serialize() | |
{ | |
var_dump('need it'); // never called | |
return serialize([ | |
$this->id, | |
$this->username, | |
$this->email, | |
$this->password, | |
// see section on salt below | |
// $this->salt, | |
]); | |
} | |
/** @see \Serializable::unserialize() */ | |
public function unserialize($serialized) | |
{ | |
list ( | |
$this->id, | |
$this->username, | |
$this->email, | |
$this->password, | |
// see section on salt below | |
// $this->salt | |
) = unserialize($serialized, ['allowed_classes' => false]); | |
} | |
/** | |
* @param mixed $password | |
* @return User | |
*/ | |
public function setPassword($password) | |
{ | |
$this->password = $password; | |
return $this; | |
} | |
/** | |
* Gets the name of the encoder used to encode the password. | |
* | |
* If the method returns null, the standard way to retrieve the encoder | |
* will be used instead. | |
* | |
* @return string | |
*/ | |
public function getEncoderName() | |
{ | |
return 'bcrypt'; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment