Skip to content

Commit aba1ba9

Browse files
committed
Create custom OAuth authenticator
1 parent 0811760 commit aba1ba9

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

config/packages/security.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ security:
3030
remember_me:
3131
secret: '%kernel.secret%'
3232
lifetime: 604800 # 1 week in seconds
33+
custom_authenticator:
34+
- CodedMonkey\Dirigent\Security\OauthAuthenticator
3335
logout:
3436
target: dashboard
3537
switch_user: true

src/Controller/OauthController.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Controller;
4+
5+
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
6+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
7+
use Symfony\Component\HttpFoundation\RedirectResponse;
8+
use Symfony\Component\Routing\Attribute\Route;
9+
10+
class OauthController extends AbstractController
11+
{
12+
#[Route('/oauth/connect/{provider}', name: 'oauth_connect')]
13+
public function connect(ClientRegistry $clientRegistry, string $provider): RedirectResponse
14+
{
15+
return $clientRegistry
16+
->getClient($provider)
17+
->redirect(scopes: [], options: []);
18+
}
19+
20+
#[Route('/oauth/check/{provider}', name: 'oauth_check')]
21+
public function check(): never
22+
{
23+
throw new \RuntimeException('should not be reached - handled by OAuth authenticator instead');
24+
}
25+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Security;
4+
5+
use CodedMonkey\Dirigent\Doctrine\Entity\User;
6+
use Doctrine\ORM\EntityManagerInterface;
7+
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
8+
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
9+
use Symfony\Component\HttpFoundation\RedirectResponse;
10+
use Symfony\Component\HttpFoundation\Request;
11+
use Symfony\Component\HttpFoundation\Response;
12+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
13+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
14+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
15+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
16+
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
17+
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
18+
19+
class OauthAuthenticator extends OAuth2Authenticator
20+
{
21+
public function __construct(
22+
private readonly ClientRegistry $clientRegistry,
23+
private readonly EntityManagerInterface $entityManager,
24+
private readonly UrlGeneratorInterface $router,
25+
) {
26+
}
27+
28+
public function supports(Request $request): ?bool
29+
{
30+
return 'oauth_check' === $request->attributes->get('_route');
31+
}
32+
33+
public function authenticate(Request $request): Passport
34+
{
35+
$providerName = $request->attributes->get('provider');
36+
$client = $this->clientRegistry->getClient($providerName);
37+
$accessToken = $this->fetchAccessToken($client);
38+
39+
return new SelfValidatingPassport(
40+
new UserBadge($accessToken->getToken(), function () use ($accessToken, $client, $providerName) {
41+
$remoteUser = $client->fetchUserFromToken($accessToken);
42+
$data = $remoteUser->toArray();
43+
44+
$user = $this->entityManager->getRepository(User::class)->findOneBy([
45+
'oauthProvider' => $providerName,
46+
'oauthSub' => $data['sub'],
47+
]);
48+
49+
if (!$user) {
50+
$user = new User();
51+
$user->setOauthProvider($providerName);
52+
$user->setOauthSub($data['sub']);
53+
}
54+
55+
$user->setUsername($data['username']);
56+
$user->setName($data['name']);
57+
$user->setEmail($data['email']);
58+
59+
$this->entityManager->persist($user);
60+
$this->entityManager->flush();
61+
62+
return $user;
63+
})
64+
);
65+
}
66+
67+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
68+
{
69+
return new RedirectResponse($this->router->generate('dashboard'));
70+
}
71+
72+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
73+
{
74+
// todo provide user with failure message if it doesn't leak any security details
75+
return new RedirectResponse($this->router->generate('dashboard_login'));
76+
}
77+
}

0 commit comments

Comments
 (0)