```
✨ feat(login): Ajoute l'authentification SSO via Keycloak et Discord
Cette modification implémente l'authentification unique (SSO) via
Keycloak et Discord, permettant aux utilisateurs de se connecter
facilement. Ajoute les trads FR.
```
This commit is contained in:
9
.env
9
.env
@@ -81,3 +81,12 @@ STRIPE_SECRET_KEY=sk_test_***
|
||||
NOTIFUSE_CLIENT_EMAIL=jovann@siteconseil.fr
|
||||
NOTIFUSE_CLIENT_SECRET=d04zCk3Fa45oOjDWHpAvc1AZxnLdGffOnNWK+Jt2yXf37+FTfuMMHb8flcfPMqLluRR3rvhbr555r6j1DEigrA==
|
||||
NOTIFUSE_WORKSPACE_ID=ecosplay
|
||||
|
||||
|
||||
KEYCLOAK_URL=https://auth.esy-web.dev
|
||||
KEYCLOAK_REALM=master
|
||||
KEYCLOAK_CLIENT_ID=ecosplay
|
||||
KEYCLOAK_CLIENT_SECRET=Cr2WycUH1Vti6xKYMOQSs0etO4tfHj1Q
|
||||
|
||||
DISCORD_CLIENT_ID=1392734559099158549
|
||||
DISCORD_CLIENT_SECRET=upafzLsVMvwX_VC0Jw6Thy7d8lPpQGts
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ext-iconv": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-zip": "*",
|
||||
"adam-paterson/oauth2-stripe": "^2.0",
|
||||
"chillerlan/php-qrcode": ">=5.0.5",
|
||||
"cocur/slugify": ">=4.6",
|
||||
"doctrine/dbal": "^3.10.3",
|
||||
@@ -26,6 +27,7 @@
|
||||
"imagine/imagine": "^1.5",
|
||||
"io-developer/php-whois": ">=4.1.10",
|
||||
"knplabs/knp-paginator-bundle": "^6.9.1",
|
||||
"knpuniversity/oauth2-client-bundle": "^2.20",
|
||||
"lasserafn/php-initial-avatar-generator": "^4.5",
|
||||
"league/flysystem-aws-s3-v3": "^3.30.1",
|
||||
"league/flysystem-bundle": "^3.6",
|
||||
@@ -46,6 +48,7 @@
|
||||
"setasign/fpdi": "^2.6.4",
|
||||
"spatie/mjml-php": "^1.2.5",
|
||||
"stancer/stancer": ">=2.0.1",
|
||||
"stevenmaguire/oauth2-keycloak": "^5.1",
|
||||
"stripe/stripe-php": "^19.0",
|
||||
"symfony/amazon-mailer": "7.3.*",
|
||||
"symfony/asset": "7.3.*",
|
||||
@@ -82,7 +85,8 @@
|
||||
"twig/intl-extra": "^3.22.1",
|
||||
"twig/twig": "^3.22",
|
||||
"vich/uploader-bundle": "^2.8.1",
|
||||
"web-auth/webauthn-lib": ">=5.2.2"
|
||||
"web-auth/webauthn-lib": ">=5.2.2",
|
||||
"wohali/oauth2-discord-new": "^1.2"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
|
||||
247
composer.lock
generated
247
composer.lock
generated
@@ -4,8 +4,67 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ed26fccb39be0438a7def45a9b987dc9",
|
||||
"content-hash": "403d821dbd4191efa1335b197d907faf",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adam-paterson/oauth2-stripe",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/adam-paterson/oauth2-stripe.git",
|
||||
"reference": "52a43a58a51dceac5d91c906c6da13f0994951b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/adam-paterson/oauth2-stripe/zipball/52a43a58a51dceac5d91c906c6da13f0994951b6",
|
||||
"reference": "52a43a58a51dceac5d91c906c6da13f0994951b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"league/oauth2-client": "^2.0",
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~0.9",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"scrutinizer/ocular": "^1.1",
|
||||
"squizlabs/php_codesniffer": "~2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"AdamPaterson\\OAuth2\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Adam Paterson",
|
||||
"email": "hello@adampaterson.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "Stripe OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
|
||||
"keywords": [
|
||||
"Authentication",
|
||||
"SSO",
|
||||
"authorization",
|
||||
"identity",
|
||||
"idp",
|
||||
"oauth",
|
||||
"oauth2",
|
||||
"single sign on",
|
||||
"stripe",
|
||||
"stripe api"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/adam-paterson/oauth2-stripe/issues",
|
||||
"source": "https://github.com/adam-paterson/oauth2-stripe/tree/master"
|
||||
},
|
||||
"time": "2018-02-26T09:35:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
"version": "1.27.1",
|
||||
@@ -4650,6 +4709,66 @@
|
||||
},
|
||||
"time": "2025-07-25T07:53:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "knpuniversity/oauth2-client-bundle",
|
||||
"version": "v2.20.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/knpuniversity/oauth2-client-bundle.git",
|
||||
"reference": "d59e4dc61484e777b6f19df2efcf8b1bcc03828a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/d59e4dc61484e777b6f19df2efcf8b1bcc03828a",
|
||||
"reference": "d59e4dc61484e777b6f19df2efcf8b1bcc03828a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"league/oauth2-client": "^2.0",
|
||||
"php": ">=8.1",
|
||||
"symfony/dependency-injection": "^6.4|^7.3|^8.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.3|^8.0",
|
||||
"symfony/http-foundation": "^6.4|^7.3|^8.0",
|
||||
"symfony/routing": "^6.4|^7.3|^8.0",
|
||||
"symfony/security-core": "^6.4|^7.3|^8.0",
|
||||
"symfony/security-http": "^6.4|^7.3|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"league/oauth2-facebook": "^1.1|^2.0",
|
||||
"symfony/phpunit-bridge": "^7.3",
|
||||
"symfony/yaml": "^6.4|^7.3|^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/security-guard": "For integration with Symfony's Guard Security layer"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"KnpU\\OAuth2ClientBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ryan Weaver",
|
||||
"email": "ryan@symfonycasts.com"
|
||||
}
|
||||
],
|
||||
"description": "Integration with league/oauth2-client to provide services",
|
||||
"homepage": "https://symfonycasts.com",
|
||||
"keywords": [
|
||||
"oauth",
|
||||
"oauth2"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues",
|
||||
"source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.20.1"
|
||||
},
|
||||
"time": "2025-12-04T15:46:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lasserafn/php-initial-avatar-generator",
|
||||
"version": "4.5",
|
||||
@@ -8437,6 +8556,67 @@
|
||||
},
|
||||
"time": "2024-11-15T17:47:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stevenmaguire/oauth2-keycloak",
|
||||
"version": "5.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stevenmaguire/oauth2-keycloak.git",
|
||||
"reference": "1b690b7377dfe7a23e1590373f37e12cf40a6d75"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/1b690b7377dfe7a23e1590373f37e12cf40a6d75",
|
||||
"reference": "1b690b7377dfe7a23e1590373f37e12cf40a6d75",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"firebase/php-jwt": "^6.0",
|
||||
"league/oauth2-client": "^2.0",
|
||||
"php": "~7.2 || ~8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~1.5.0",
|
||||
"phpunit/phpunit": "~9.6.4",
|
||||
"squizlabs/php_codesniffer": "~3.7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stevenmaguire\\OAuth2\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Steven Maguire",
|
||||
"email": "stevenmaguire@gmail.com",
|
||||
"homepage": "https://github.com/stevenmaguire"
|
||||
}
|
||||
],
|
||||
"description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
|
||||
"keywords": [
|
||||
"authorisation",
|
||||
"authorization",
|
||||
"client",
|
||||
"keycloak",
|
||||
"oauth",
|
||||
"oauth2"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues",
|
||||
"source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/5.1.0"
|
||||
},
|
||||
"time": "2023-10-24T06:10:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v19.0.0",
|
||||
@@ -15230,6 +15410,71 @@
|
||||
"source": "https://github.com/webmozarts/assert/tree/1.12.1"
|
||||
},
|
||||
"time": "2025-10-29T15:56:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "wohali/oauth2-discord-new",
|
||||
"version": "1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wohali/oauth2-discord-new.git",
|
||||
"reference": "2df4d2a882e04c749880797704e4bde8f00ea1d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/wohali/oauth2-discord-new/zipball/2df4d2a882e04c749880797704e4bde8f00ea1d9",
|
||||
"reference": "2df4d2a882e04c749880797704e4bde8f00ea1d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"league/oauth2-client": "^2.0",
|
||||
"php": "^7.2|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"team-reflex/oauth2-discord": ">=1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~1.3.0",
|
||||
"php-parallel-lint/php-parallel-lint": "~0.9",
|
||||
"phpunit/phpunit": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Wohali\\OAuth2\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joan Touzet",
|
||||
"email": "code@atypical.net",
|
||||
"homepage": "https://github.com/wohali"
|
||||
}
|
||||
],
|
||||
"description": "Discord OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
|
||||
"keywords": [
|
||||
"authorisation",
|
||||
"authorization",
|
||||
"client",
|
||||
"discord",
|
||||
"oauth",
|
||||
"oauth2"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/wohali/oauth2-discord-new/issues",
|
||||
"source": "https://github.com/wohali/oauth2-discord-new/tree/1.2.1"
|
||||
},
|
||||
"time": "2022-12-29T18:45:10+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
||||
@@ -19,4 +19,5 @@ return [
|
||||
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
|
||||
Sentry\SentryBundle\SentryBundle::class => ['prod' => true],
|
||||
PixelOpen\CloudflareTurnstileBundle\PixelOpenCloudflareTurnstileBundle::class => ['all' => true],
|
||||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
14
config/packages/knpu_oauth2_client.yaml
Normal file
14
config/packages/knpu_oauth2_client.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
knpu_oauth2_client:
|
||||
clients:
|
||||
keycloak:
|
||||
type: keycloak
|
||||
auth_server_url: '%env(KEYCLOAK_URL)%'
|
||||
realm: '%env(KEYCLOAK_REALM)%'
|
||||
client_id: '%env(KEYCLOAK_CLIENT_ID)%'
|
||||
client_secret: '%env(KEYCLOAK_CLIENT_SECRET)%'
|
||||
redirect_route: connect_keycloak_check
|
||||
discord:
|
||||
type: discord
|
||||
client_id: '%env(DISCORD_CLIENT_ID)%'
|
||||
client_secret: '%env(DISCORD_CLIENT_SECRET)%'
|
||||
redirect_route: connect_discord_check
|
||||
@@ -27,6 +27,8 @@ security:
|
||||
entry_point: App\Security\AuthenticationEntryPoint
|
||||
custom_authenticator:
|
||||
- App\Security\LoginFormAuthenticator
|
||||
- App\Security\KeycloakAuthenticator
|
||||
- App\Security\DiscordAuthenticator
|
||||
logout:
|
||||
target: app_logout
|
||||
|
||||
|
||||
32
migrations/Version20260111130759.php
Normal file
32
migrations/Version20260111130759.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260111130759 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE account ALTER password DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE "account" ALTER password SET NOT NULL');
|
||||
}
|
||||
}
|
||||
32
src/Controller/KeycloakController.php
Normal file
32
src/Controller/KeycloakController.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
// src/Controller/KeycloakController.php
|
||||
namespace App\Controller;
|
||||
|
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class KeycloakController extends AbstractController
|
||||
{
|
||||
#[Route('/connect/keycloak', name: 'connect_keycloak_start')]
|
||||
public function connectActionKeycloak(ClientRegistry $clientRegistry)
|
||||
{
|
||||
// Redirige vers Keycloak
|
||||
return $clientRegistry->getClient('keycloak')->redirect(['openid', 'profile', 'email'], []);
|
||||
}
|
||||
#[Route('/connect/discord', name: 'connect_discord_start')]
|
||||
public function connectActionDiscord(ClientRegistry $clientRegistry)
|
||||
{
|
||||
// Redirige vers Keycloak
|
||||
return $clientRegistry->getClient('discord')->redirect(['openid', 'email','identify'], []);
|
||||
}
|
||||
|
||||
#[Route('/oauth/keycloak', name: 'connect_keycloak_check')]
|
||||
#[Route('/oauth/discord', name: 'connect_discord_check')]
|
||||
public function connectCheckAction(Request $request)
|
||||
{
|
||||
// Cette méthode reste vide, elle est interceptée par l'Authenticator
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, \Ser
|
||||
#[ORM\Column]
|
||||
private array $roles = [];
|
||||
|
||||
#[ORM\Column]
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?string $password = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
|
||||
88
src/Security/DiscordAuthenticator.php
Normal file
88
src/Security/DiscordAuthenticator.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// src/Security/KeycloakAuthenticator.php
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\Account;
|
||||
use App\Entity\User; // Votre entité User
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||
use Wohali\OAuth2\Client\Provider\DiscordResourceOwner;
|
||||
|
||||
class DiscordAuthenticator extends OAuth2Authenticator
|
||||
{
|
||||
use TargetPathTrait;
|
||||
|
||||
public function __construct(
|
||||
private ClientRegistry $clientRegistry,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private RouterInterface $router
|
||||
) {}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
// S'active uniquement sur la route de check
|
||||
return $request->attributes->get('_route') === 'connect_discord_check';
|
||||
}
|
||||
|
||||
public function authenticate(Request $request): SelfValidatingPassport
|
||||
{
|
||||
$client = $this->clientRegistry->getClient('discord');
|
||||
$accessToken = $this->fetchAccessToken($client);
|
||||
|
||||
return new SelfValidatingPassport(
|
||||
new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
|
||||
/** @var DiscordResourceOwner $discordUser */
|
||||
$discordUser = $client->fetchUserFromToken($accessToken);
|
||||
$email = $discordUser->getEmail();
|
||||
|
||||
// 1) Chercher l'utilisateur par son ID Keycloak ou son Email
|
||||
$user = $this->entityManager->getRepository(Account::class)->findOneBy(['email' => $email]);
|
||||
|
||||
// 2) Créer l'utilisateur s'il n'existe pas (Inscription automatique)
|
||||
if (!$user) {
|
||||
$user = new Account();
|
||||
$user->setEmail($email);
|
||||
$user->setUsername($email);
|
||||
$user->setUuid(Uuid::uuid4()->toString());
|
||||
$user->setRoles(['ROLE_USER']);
|
||||
$user->setIsActif(true);
|
||||
// On peut mapper d'autres champs :
|
||||
// $user->setFirstName($keycloakUser->toArray()['given_name']);
|
||||
$this->entityManager->persist($user);
|
||||
}
|
||||
|
||||
// 3) Mettre à jour les rôles ou infos si nécessaire
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $user;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||
{
|
||||
// Redirige vers la page demandée initialement ou l'accueil
|
||||
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
|
||||
return new RedirectResponse($targetPath);
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->router->generate('app_home'));
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
|
||||
return new Response($message, Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
87
src/Security/KeycloakAuthenticator.php
Normal file
87
src/Security/KeycloakAuthenticator.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// src/Security/KeycloakAuthenticator.php
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\Account;
|
||||
use App\Entity\User; // Votre entité User
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
|
||||
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||
|
||||
class KeycloakAuthenticator extends OAuth2Authenticator
|
||||
{
|
||||
use TargetPathTrait;
|
||||
|
||||
public function __construct(
|
||||
private ClientRegistry $clientRegistry,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private RouterInterface $router
|
||||
) {}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
// S'active uniquement sur la route de check
|
||||
return $request->attributes->get('_route') === 'connect_keycloak_check';
|
||||
}
|
||||
|
||||
public function authenticate(Request $request): SelfValidatingPassport
|
||||
{
|
||||
$client = $this->clientRegistry->getClient('keycloak');
|
||||
$accessToken = $this->fetchAccessToken($client);
|
||||
|
||||
return new SelfValidatingPassport(
|
||||
new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
|
||||
/** @var \Stevenmaguire\OAuth2\Client\Provider\KeycloakResourceOwner $keycloakUser */
|
||||
$keycloakUser = $client->fetchUserFromToken($accessToken);
|
||||
$email = $keycloakUser->getEmail();
|
||||
|
||||
// 1) Chercher l'utilisateur par son ID Keycloak ou son Email
|
||||
$user = $this->entityManager->getRepository(Account::class)->findOneBy(['email' => $email]);
|
||||
|
||||
// 2) Créer l'utilisateur s'il n'existe pas (Inscription automatique)
|
||||
if (!$user) {
|
||||
$user = new Account();
|
||||
$user->setEmail($email);
|
||||
$user->setUsername($keycloakUser->toArray()['preferred_username']);
|
||||
$user->setUuid(Uuid::uuid4()->toString());
|
||||
$user->setRoles(['ROLE_USER']);
|
||||
$user->setIsActif(true);
|
||||
// On peut mapper d'autres champs :
|
||||
// $user->setFirstName($keycloakUser->toArray()['given_name']);
|
||||
$this->entityManager->persist($user);
|
||||
}
|
||||
|
||||
// 3) Mettre à jour les rôles ou infos si nécessaire
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $user;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||
{
|
||||
// Redirige vers la page demandée initialement ou l'accueil
|
||||
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
|
||||
return new RedirectResponse($targetPath);
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->router->generate('app_home'));
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
|
||||
return new Response($message, Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
12
symfony.lock
12
symfony.lock
@@ -50,6 +50,18 @@
|
||||
"knplabs/knp-paginator-bundle": {
|
||||
"version": "v6.8.1"
|
||||
},
|
||||
"knpuniversity/oauth2-client-bundle": {
|
||||
"version": "2.20",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.20",
|
||||
"ref": "1ff300d8c030f55c99219cc55050b97a695af3f6"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/knpu_oauth2_client.yaml"
|
||||
]
|
||||
},
|
||||
"league/flysystem-bundle": {
|
||||
"version": "3.4",
|
||||
"recipe": {
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# FORMULAIRE #}
|
||||
{# FORMULAIRE CLASSIQUE #}
|
||||
<form action="{{ path('app_login') }}" method="post" class="space-y-6">
|
||||
<input type="hidden" name="remember" value="true">
|
||||
|
||||
@@ -103,7 +103,27 @@
|
||||
<i class="fas fa-chevron-right ml-3 mt-1 group-hover:translate-x-2 transition-transform"></i>
|
||||
</button>
|
||||
</form>
|
||||
{# SÉPARATEUR #}
|
||||
<div class="relative my-10">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t-4 border-gray-900"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-xs uppercase font-black">
|
||||
<span class="px-4 bg-white text-gray-900">{{ 'common.or'|trans }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{# BOUTON KEYCLOAK (SSO) - Placé en haut pour favoriser le SSO #}
|
||||
<a href="{{ path('connect_keycloak_start') }}"
|
||||
class="w-full flex items-center justify-center py-4 px-4 border-4 border-gray-900 text-lg font-black uppercase tracking-widest text-gray-900 bg-yellow-400 shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
<i class="fas fa-key mr-3"></i>
|
||||
{{ 'button.login_keycloak'|trans|default('Connexion Keycloak') }}
|
||||
</a>
|
||||
|
||||
<a href="{{ path('connect_discord_start') }}"
|
||||
class="mt-2 w-full flex items-center justify-center py-4 px-4 border-4 border-gray-900 text-lg font-black uppercase tracking-widest text-gray-900 bg-yellow-400 shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
<i class="fas fa-key mr-3"></i>
|
||||
{{ 'button.login_discord'|trans|default('Connexion Keycloak') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1225,3 +1225,8 @@ newsletter:
|
||||
message: "Ton inscription a été validée avec succès. Bienvenue dans la communauté !"
|
||||
hint: "Prépare-toi à recevoir le meilleur du Cosplay directement dans ta boîte."
|
||||
back_button: "C'EST PARTI !"
|
||||
button:
|
||||
login_keycloak: 'SSO'
|
||||
login_discord: 'Discord'
|
||||
common:
|
||||
or: 'Ou'
|
||||
|
||||
Reference in New Issue
Block a user