diff --git a/docs/Client_Setup.md b/docs/Client_Setup.md new file mode 100644 index 0000000..51917c0 --- /dev/null +++ b/docs/Client_Setup.md @@ -0,0 +1,327 @@ +# 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 +The 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 +```