# 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
```