Skip to content

Instantly share code, notes, and snippets.

@jonathangreco
Created May 31, 2018 06:32
Show Gist options
  • Save jonathangreco/b21dd8280928028828aa438a45fdb65a to your computer and use it in GitHub Desktop.
Save jonathangreco/b21dd8280928028828aa438a45fdb65a to your computer and use it in GitHub Desktop.
Authentication issue
<?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;
}
}
<?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');
}
}
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 }
<?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