Skip to content

Instantly share code, notes, and snippets.

@kunicmarko20
Last active March 25, 2024 10:56
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save kunicmarko20/a228dfcc1720a29283c900458e6c6a0b to your computer and use it in GitHub Desktop.
Save kunicmarko20/a228dfcc1720a29283c900458e6c6a0b to your computer and use it in GitHub Desktop.
Symfony OAuth with Leage OAuth2 library
<a href="{{ googleAuthorizationUrl }}">
Login
</a>
<?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(),
]
));
}
}
<?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;
}
}
auth.login:
path: /login
controller: App\Login
methods: [GET]
auth.logout:
path: /logout
methods: [GET]
auth.connect.google:
path: /connect/google
methods: [GET]
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 }
<?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
{
}
}
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)%'
<?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;
}
}
@kunicmarko20
Copy link
Author

Use League OAuth2 library directly with Symfony without the need for external bundles

@ruudk
Copy link

ruudk commented Jan 14, 2020

Thanks for sharing! This is much simpler than having to rely on external dependencies like HWIOAuthBundle or knpuniversity/oauth2-client-bundle.

@Tojo-srg
Copy link

Thanks for sharing!

@Mouerr
Copy link

Mouerr commented Jan 15, 2020

thanks for sharing, amazing job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment