Skip to content

Instantly share code, notes, and snippets.

@bigfoot90
Created February 13, 2020 20:50
Show Gist options
  • Save bigfoot90/9f7ebaeee1466d6698ef15a2a2284d31 to your computer and use it in GitHub Desktop.
Save bigfoot90/9f7ebaeee1466d6698ef15a2a2284d31 to your computer and use it in GitHub Desktop.
Symfony Telegram Authenticator
<?php
namespace App\Security;
use App\Models\Entity\User;
use App\Models\ValueObject\TelegramAccount;
use Doctrine\ORM\EntityManagerInterface;
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\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;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
final class TelegramAuthenticator extends AbstractGuardAuthenticator
{
use TargetPathTrait;
private EntityManagerInterface $entityManager;
private UrlGeneratorInterface $urlGenerator;
private string $secret;
private const REQUIRED_FIELDS = [
'id',
'first_name',
'last_name',
'auth_date',
'hash',
];
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, string $telegramBotToken)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->secret = hash('sha256', $telegramBotToken, true);
}
/**
* {@inheritdoc}
*/
public function supports(Request $request)
{
$route = $request->attributes->get('_route');
$data = $request->query->all();
return $route === '_telegram_login'
&& array_intersect(static::REQUIRED_FIELDS, array_keys($data)) === static::REQUIRED_FIELDS;
}
/**
* {@inheritdoc}
*/
public function getCredentials(Request $request)
{
$credentials = $request->query->all();
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username'] ?? null
);
return $credentials;
}
/**
* {@inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$this->validate($credentials);
$user = $this->entityManager->getRepository(User::class)->findOneBy(['telegram.id' => $credentials['id']]);
// $user = $userProvider->loadUserByUsername($credentials['username']);
if (!$user) {
// If a User was not found in our database, return a new instance created on the fly,
// you should then persist it by yourself.
return new User(new TelegramAccount(
$credentials['id'],
$credentials['first_name'].' '.$credentials['last_name'],
$credentials['username'] ?? null,
'en',
$credentials['photo_url'] ?? null
));
}
return $user;
}
/**
* Check the data integrity.
*/
private function validate(array $data)
{
// Check for data expiration
// This is optional, but recommended step ;)
if ((time() - $data['auth_date']) > 3600) {
throw new CustomUserMessageAuthenticationException('Login data expired');
}
$hash = $data['hash'];
unset($data['hash']);
ksort($data);
$data_check_string = implode("\n", array_map(
function ($key, $value) { return "$key=$value"; },
array_keys($data),
$data
));
// Check for data integrity
$hmac = hash_hmac('sha256', $data_check_string, $this->secret);
if ($hmac !== $hash) {
throw new CustomUserMessageAuthenticationException('Invalid data checksum');
}
}
/**
* {@inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
// The check was done by `validate` method
return true;
}
/**
* {@inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->getLoginUrl();
return new RedirectResponse($url);
}
/**
* {@inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('user_profile'));
}
/**
* {@inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): RedirectResponse
{
if ($request->hasSession()) {
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
}
$url = $this->getLoginUrl();
return new RedirectResponse($url);
}
/**
* {@inheritdoc}
*/
public function supportsRememberMe()
{
return true;
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('login');
}
}
@denistouch
Copy link

How it`s work in this case ?

telegram request query = [
        'id',
        'first_name',
        'photo_url'
        'auth_date',
        'hash',
    ];

I got a real problem when Telegram send me reqest without last_name

@bigfoot90
Copy link
Author

@denistouch I'm not sure. Try to remove last_name from required fields.

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