Skip to content

Instantly share code, notes, and snippets.

@kunicmarko20
Last active March 25, 2024 10:56
Show Gist options
  • Select an option

  • Save kunicmarko20/a228dfcc1720a29283c900458e6c6a0b to your computer and use it in GitHub Desktop.

Select an option

Save kunicmarko20/a228dfcc1720a29283c900458e6c6a0b to your computer and use it in GitHub Desktop.

Revisions

  1. kunicmarko20 revised this gist Jan 14, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion routes.yaml
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    auth.login:
    path: /login
    controller: Level\Admin\Ui\Action\Auth\Login
    controller: App\Login
    methods: [GET]

    auth.logout:
  2. kunicmarko20 revised this gist Jan 14, 2020. 4 changed files with 65 additions and 0 deletions.
    38 changes: 38 additions & 0 deletions Login.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    <?php

    declare(strict_types=1);

    namespace App;

    use Symfony\Component\HttpFoundation\Response;
    use Twig\Environment;
    use League\OAuth2\Client\Provider\Google as GoogleProvider;

    final class Login
    {
    /**
    * @var Environment
    */
    private $twig;

    /**
    * @var GoogleProvider
    */
    private $provider;

    public function __construct(Environment $twig, GoogleProvider $provider)
    {
    $this->twig = $twig;
    $this->provider = $provider;
    }

    public function __invoke(): Response
    {
    return new Response($this->twig->render(
    'login.html.twig',
    [
    'googleAuthorizationUrl' => $this->provider->getAuthorizationUrl(),
    ]
    ));
    }
    }
    3 changes: 3 additions & 0 deletions login.html.twig
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    <a href="{{ googleAuthorizationUrl }}">
    Login
    </a>
    12 changes: 12 additions & 0 deletions routes.yaml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    auth.login:
    path: /login
    controller: Level\Admin\Ui\Action\Auth\Login
    methods: [GET]

    auth.logout:
    path: /logout
    methods: [GET]

    auth.connect.google:
    path: /connect/google
    methods: [GET]
    12 changes: 12 additions & 0 deletions services.yaml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    services:
    _defaults:
    autowire: true
    public: false
    autoconfigure: true

    League\OAuth2\Client\Provider\Google:
    arguments:
    $options:
    clientId: '%env(APP_OAUTH_GOOGLE_CLIENT_ID)%'
    clientSecret: '%env(APP_OAUTH_GOOGLE_CLIENT_SECRET)%'
    redirectUri: '%env(APP_OAUTH_GOOGLE_REDIRECT_URI)%'
  3. kunicmarko20 created this gist Jan 14, 2020.
    114 changes: 114 additions & 0 deletions OAuthAuthenticator.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,114 @@
    <?php

    declare(strict_types=1);

    namespace App;

    use League\OAuth2\Client\Provider\Google as GoogleProvider;
    use League\OAuth2\Client\Provider\GoogleUser;
    use League\OAuth2\Client\Token\AccessTokenInterface;
    use Symfony\Component\HttpFoundation\RedirectResponse;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Session\SessionInterface;
    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\Exception\CustomUserMessageAuthenticationException;
    use Symfony\Component\Security\Core\Security;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
    use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;

    final class OAuthAuthenticator extends AbstractGuardAuthenticator
    {
    private const ROUTE_LOGIN = 'auth.login';

    private const ROUTE_OAUTH_LOGIN = 'auth.connect.google';

    private const ROUTE_DASHBOARD = 'index';

    /**
    * @var GoogleProvider
    */
    private $provider;

    /**
    * @var UrlGeneratorInterface
    */
    private $urlGenerator;

    public function __construct(GoogleProvider $provider, UrlGeneratorInterface $urlGenerator)
    {
    $this->provider = $provider;
    $this->urlGenerator = $urlGenerator;
    }

    public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
    {
    return new RedirectResponse(
    $this->urlGenerator->generate(self::ROUTE_LOGIN)
    );
    }

    public function supports(Request $request): bool
    {
    return $request->attributes->get('_route') === self::ROUTE_OAUTH_LOGIN;
    }

    public function getCredentials(Request $request): AccessTokenInterface
    {
    if (!$request->query->has('code')) {
    throw new CustomUserMessageAuthenticationException('Missing code.');
    }

    return $this->provider->getAccessToken('authorization_code', [
    'code' => $request->query->get('code')
    ]);
    }

    public function getUser($credentials, UserProviderInterface $userProvider): UserInterface
    {
    $googleUser = $this->provider->getResourceOwner($credentials);

    \assert($googleUser instanceof GoogleUser);

    try {
    $email = $googleUser->getEmail();

    \assert(\is_string($email));

    return $userProvider->loadUserByUsername($email);
    } catch (UserDoesNotExist $exception) {
    throw new CustomUserMessageAuthenticationException($exception->getMessage());
    }
    }

    public function checkCredentials($credentials, UserInterface $user): bool
    {
    return true;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): RedirectResponse
    {
    $session = $request->getSession();

    \assert($session instanceof SessionInterface);
    $session->set(Security::AUTHENTICATION_ERROR, $exception);

    return new RedirectResponse(
    $this->urlGenerator->generate(self::ROUTE_LOGIN)
    );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse
    {
    return new RedirectResponse(
    $this->urlGenerator->generate(self::ROUTE_DASHBOARD)
    );
    }

    public function supportsRememberMe(): bool
    {
    return true;
    }
    }
    54 changes: 54 additions & 0 deletions SecurityUser.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,54 @@
    <?php

    declare(strict_types=1);

    namespace App;

    use App\User;
    use Symfony\Component\Security\Core\User\UserInterface;

    final class SecurityUser implements UserInterface
    {
    /**
    * @var string
    */
    private $email;

    /**
    * @var string[]
    */
    private $roles;

    public function __construct(User $user)
    {
    $this->email = $user->email();
    $this->roles = $user->roles();;
    }

    /**
    * @return string[]
    */
    public function getRoles(): array
    {
    return $this->roles;
    }

    public function getPassword(): string
    {
    return '';
    }

    public function getSalt(): string
    {
    return '';
    }

    public function getUsername(): string
    {
    return $this->email->toString();
    }

    public function eraseCredentials(): void
    {
    }
    }
    39 changes: 39 additions & 0 deletions UserProvider.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    <?php

    declare(strict_types=1);

    namespace App;

    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;

    final class UserProvider implements UserProviderInterface
    {
    /**
    * @var UserRepository
    */
    private $userRepository;

    public function __construct(UserRepository $userRepository)
    {
    $this->userRepository = $userRepository;
    }

    /**
    * @param string $email
    */
    public function loadUserByUsername($email): SecurityUser
    {
    return new SecurityUser($this->userRepository->getOneByEmail(new Email($email)));
    }

    public function refreshUser(UserInterface $user): SecurityUser
    {
    return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class): bool
    {
    return $class === SecurityUser::class;
    }
    }
    27 changes: 27 additions & 0 deletions security.yaml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    security:
    providers:
    users:
    id: App\UserProvider

    firewalls:
    login:
    pattern: /login
    anonymous: true

    admin:
    pattern: ^/
    provider: users
    guard:
    authenticators:
    - App\OAuthAuthenticator
    logout:
    path: auth.logout
    target: auth.login

    main:
    anonymous: ~

    access_control:
    - { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/connect/google, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/.*, role: IS_AUTHENTICATED_FULLY }