# Client setup ## Add needed dependencies ```bash composer require nelmio/cors-bundle composer require knpuniversity/oauth2-client-bundle ``` ## Configure the bundle ### nelmio/cors-bundle ```yaml # config/packages/nelmio_cors.yaml nelmio_cors: defaults: origin_regex: true allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] allow_headers: ['Content-Type', 'Authorization'] expose_headers: ['Link'] max_age: 3600 paths: '^/token$': origin_regex: true allow_origin: ['*'] allow_headers: ['Content-Type', 'Authorization'] allow_methods: ['POST', 'OPTIONS'] allow_credentials: true max_age: 3600 '^/authorize$': origin_regex: true allow_origin: ['*'] allow_headers: ['Content-Type', 'Authorization'] allow_methods: ['GET', 'POST', 'OPTIONS'] allow_credentials: true max_age: 3600 ``` ### knpuniversity/oauth2-client-bundle ```yaml # config/packages/knpu_oauth2_client.yaml knpu_oauth2_client: clients: # configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration sudalys: type: generic provider_class: Sudalys\OAuth2\Client\Provider\Sudalys client_id: '%env(OAUTH2_CLIENT_ID)%' client_secret: '%env(OAUTH2_CLIENT_SECRET)%' redirect_route: uri # The route to redirect to after authentication (must match the one in the server DB uri DB) provider_options: { domain: } use_state: false ``` ### .env ```dotenv # .env # CORS CORS_ALLOW_ORIGIN=http://*.your domain/*' # OAUTH2 OAUTH2_CLIENT_ID= OAUTH2_CLIENT_SECRET= ``` Copy and paste the client library then modify the conposer.json autoloard directive to include the new library ```json "autoload": { "psr-4": { "App\\": "src/", "Sudalys\\OAuth2\\Client\\": "libs/sudalys/oauth2-client/src" } }, ``` ```php clientRegistry = $clientRegistry; $this->em = $em; $this->router = $router; $this->urlGenerator = $urlGenerator; } public function start(Request $request, AuthenticationException $authException = null): Response { // Use the KnpU client to generate the correct authorization URL, // including state / redirect_uri / scope / pkce as configured. $client = $this->getSudalysClient(); // Option A: let the client use the configured redirect uri and default scopes: return $client->redirect(); // Option B (explicit): specify scopes and an explicit redirect_uri (absolute URL) // $redirectUri = $this->urlGenerator->generate('sudalys_check', [], UrlGeneratorInterface::ABSOLUTE_URL); // return $client->redirect(['openid', 'profile'], ['redirect_uri' => $redirectUri]); } public function supports(Request $request): ?bool { // If your OAuth redirect route is named 'sudalys_check', check by route: if ($request->attributes->get('_route') === 'sudalys_check') { return true; } // fallback: also support requests containing the authorization code return (bool) $request->query->get('code'); } public function authenticate(Request $request): Passport { $client = $this->getSudalysClient(); $accessToken = $this->fetchAccessToken($client); $session = $request->getSession(); $session->set('access_token', $accessToken->getToken()); // Stocker également le refresh token s'il est disponible if ($accessToken->getRefreshToken()) { $session->set('refresh_token', $accessToken->getRefreshToken()); } return new SelfValidatingPassport( new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) { //show in log the access token $sudalysSsoUser = $client->fetchUserFromToken($accessToken); $ssoId = $sudalysSsoUser->getId(); /* * On regarde si le token est valide */ if($accessToken->getExpires() > time()) { // Token valide, on regarde si l'utilisateur existe en bdd locale /** @var User $userInDatabase */ $user = $this->em->getRepository(User::class)->findOneBy(['ssoId' => $ssoId]); /** * on cree l'utilisateur s'il n'existe pas **/ if (!$user) { $user = new User(); $user->setEmail($sudalysSsoUser->getEmail()); $user->setName($sudalysSsoUser->getName()); $user->setSurname($sudalysSsoUser->getSurname()); $user->setSsoId($sudalysSsoUser->getId()); $this->em->persist($user); }else{ // On met a jour l'utilisateur $user->setEmail($sudalysSsoUser->getEmail()); $user->setName($sudalysSsoUser->getName()); $user->setSurname($sudalysSsoUser->getSurname()); $this->em->persist($user); } $this->em->flush(); return $user; } }) ); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { // change "app_homepage" to some route in your app $targetUrl = $this->router->generate('app_index'); return new RedirectResponse($targetUrl); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { $message = strtr($exception->getMessageKey(), $exception->getMessageData()); return new Response($message, Response::HTTP_FORBIDDEN); } /** * */ private function getSudalysClient() { return $this->clientRegistry->getClient('sudalys'); } } ``` ```php namespace App\Security\SsoAuthenticator; public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { // change "app_homepage" to some route in your app $targetUrl = $this->router->generate('your redirect route'); return new RedirectResponse($targetUrl); } ``` ### Security.yaml ```yaml app_user_provider: entity: class: App\Entity\User property: email firewalls: main: lazy: true provider: app_user_provider custom_authenticators: - App\Security\SsoAuthenticator entry_point: App\Security\SsoAuthenticator logout: path: app_logout target: app_after_logout invalidate_session: true delete_cookies: ['PHPSESSID'] access_control: - { path: ^/sso/login, roles: PUBLIC_ACCESS } - { path: ^/sso/check, roles: PUBLIC_ACCESS } - { path: ^/, roles: IS_AUTHENTICATED_FULLY } ``` ### Setup oauth controller ```php getClient('sudalys')->redirect(); } #[Route('/sso/check', name: 'sudalys_sso_check')] public function connectCheckAction(Request $request) { return $this->redirectToRoute('app_index'); } #[Route('/logout', name: 'app_logout')] public function logout(): void { throw new \Exception('This should never be reached!'); } #[Route('/logout-redirect', name: 'app_after_logout')] public function afterLogout(): RedirectResponse { // SSO logout URL — adjust if necessary $ssoLogout = 'http://portail.solutions-easy.moi/sso_logout'; return new RedirectResponse($ssoLogout); } } ``` # Server setup ## Create OAuth2 client ```cmd php bin/console league:oauth2-server:create-client --redirect-uri="http://your-client-domain/sso/check" --scope="openid" --scope="profile" --scope="email" --grant-type=authorization_code ``` If there is a scope or grand error, delete the client do the following first ```cmd php bin/console league:oauth2-server:delete-client ``` Identifier can be found in the database oauth2_client table To recreate the client and enter the scopes and grant types after creating the client directly in the db ```text scopes = email profile openid grants = authorization_code ```