# Authentification FranceConnect dans WebCOnsumerApp via TmsSsoClientBUndle
## TmsSsoClientBundle
### 1. Configuration
#### a- TmsSsoClientBundle/Resources/config/routing.yml
france_connect_login:
path: /login/check-france-connect
#### b- TmsSsoClientBundle/Resources/config/services.yml
hwi_oauth.resource_owner.tms_france_connect.class: Tms\Bundle\SsoClientBundle\OAuth\ResourceOwner\TmsFranceConnectResourceOwner
...
wi_oauth.abstract_resource_owner.tms_france_connect:
class: '%hwi_oauth.resource_owner.tms_france_connect.class%'
parent: hwi_oauth.abstract_resource_owner.oauth2
abstract: true
### 2. ResourceOwner
#### a-TmsSsoClientBundle/OAuth/ResourceOwner/TmsFranceConnectResourceOwner.php
##### * Les options
'client_id' : identifiant fourni par France Connect,
'client_secret' : code fourni par France Connect,
'open_id_session_token' : récupere par la fonction getRandomToken(),
'open_id_session_nonce' => récupere par la fonction getRandomToken(),
'base_url' : dev => https://fcp.integ01.dev-franceconnect.fr/api/v1/a, prod => https://app.franceconnect.gouv.fr/api/v1/
'display' => null,
'sup_scope' => null,
'scope' => null,
'csrf' => false,
'user_response_class' => 'HWI\Bundle\OAuthBundle\OAuth\Response\PathUserResponse',
'auth_with_one_url' => false,
'access_token_url' => false,
'use_bearer_authorization' => null,
'infos_url' : dev => https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo, prod => https://app.franceconnect.gouv.fr/api/v1/userinfo
"certificates" : chemain vers le fichier certificates.pem
'api_token' : token générer du manager pour authoriser les signin dans SsoManager
##### * Execution:
1- getAuthorizationUrl
public function getAuthorizationUrl($redirectUri, array $extraParameters = array())
{
$state = "token={$this->options['open_id_session_token']}";
$scope = "openid%20".str_replace(",","%20",$this->options['sup_scope']).'%20';
$info=array("response_type"=>'code',
"client_id"=> $this->options['client_id'],
"scope"=>$scope,
"redirect_uri"=> $redirectUri,
"state"=>urlencode($state),
"nonce"=> $this->options['open_id_session_nonce']
);
$url = $this->getURLforService("authorize");
foreach($info as $key=>$value){
$url.=$key."=".$value."&";
}
return $url;
}
créer l'url de redirection vers les ervice FranceConnect.
exemple :
https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize?client_id=123456&redirect_uri=http://aide-installation-personnel.odr.docker/&scope=openid%20profile%20email%20address%20phone%20preferred_username%20
2- Choix du service d'authentification

3- Se connecter

3- retour sur le site ($redirectUri qui daot etres renseigner sur https://partenaires.franceconnect.gouv.fr/)

4- Recuperation du token d'accée
public function getAccessToken(Request $request, $redirectUri, array $extraParameters = array())
{
$parameters = array_merge(array(
'code' => $request->query->get('code'),
'grant_type' => 'authorization_code',
'client_id' => $this->options['client_id'],
'client_secret' => $this->options['client_secret'],
'redirect_uri' => $redirectUri,
), $extraParameters);
$response = $this->doGetTokenRequest($this->options['access_token_url'], $parameters);
$response = $this->getCurlWrapperResponseContent($response);
$this->validateResponseContent($response);
return $response;
}
Récuperation du token par la fonction "doGetTokenRequest" via l'url de l'option "access_token_url" en ajoutant comme parameters :
- code : fournit par france connect
- client_id
- client_secret
- redirect_uri
protected function doGetTokenRequest($url, array $parameters = array())
{
return $this->httpRequest($url, $parameters);
}
protected function httpRequest($url, $content = array(), $headers = array(), $method = null)
{
if (null === $method) {
$method = null === $content ? CurlWrapper::METHOD_GET : CurlWrapper::METHOD_POST;
}
$curlWrapper = new CurlWrapper();
$curlWrapper->setServerCertificate($this->options['certificates']);
$curlWrapper->setPostDataUrlEncode($content);
$token_url = $this->options['access_token_url'];
$result = $curlWrapper->get($token_url);
return $result;
}
La fonction httpRequest signe les headers de la requette avec la certificat "certificates"
5- Récuperation du contenu de la reponse via la fonction getCurlWrapperResponseContent
protected function getCurlWrapperResponseContent($content)
{
if (!$content) {
return array();
}
$response = json_decode($content, true);
if (JSON_ERROR_NONE !== json_last_error()) {
parse_str($content, $response);
}
return $response;
}
6- Validation du contenu de la reponse
protected function validateResponseContent($response)
{
if (isset($response['error_description'])) {
throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['error_description']));
}
if (isset($response['error'])) {
throw new AuthenticationException(sprintf('OAuth error: "%s"', isset($response['error']['message']) ? $response['error']['message'] : $response['error']));
}
if (!isset($response['access_token'])) {
throw new AuthenticationException('Not a valid access token.');
}
}
7- Recuperation des infos utilisateur via la fonction getUserInformation
public function getUserInformation(array $accessToken, array $extraParameters = array())
{
$curlWrapper = new CurlWrapper();
$curlWrapper->setServerCertificate($this->options['certificates']);
$curlWrapper->addHeader("Authorization", "Bearer ".$accessToken['access_token']);
$user_info_url = $this->getURLforService("userinfo");
$result = $curlWrapper->get($user_info_url);
$response = $this->getUserResponse();
$response->setResponse($result);
$response->setResourceOwner($this);
$response->setOAuthToken(new OAuthToken($accessToken));
return $response;
}
#### d- TmsSsoClientBundle/Manager/CurlWrapper.php
<?php
/**
* Created by PhpStorm.
* User: tveron
* Date: 01/08/2016
* Time: 12:00
*/
namespace Tms\Bundle\SsoClientBundle\Manager;
class CurlWrapper
{
const POST_DATA_SEPARATOR = "\r\n";
private $curlHandle;
private $lastError;
private $postData;
private $postFile;
private $postFileProperties;
const METHOD_OPTIONS = 'OPTIONS';
const METHOD_GET = 'GET';
const METHOD_HEAD = 'HEAD';
const METHOD_POST = 'POST';
const METHOD_PUT = 'PUT';
const METHOD_DELETE = 'DELETE';
const METHOD_PATCH = 'PATCH';
public function __construct($proxy = null)
{
$this->curlHandle = curl_init();
$this->setProperties(CURLOPT_RETURNTRANSFER, 1);
$this->setProperties(CURLOPT_FOLLOWLOCATION, 1);
$this->setProperties(CURLOPT_MAXREDIRS, 5);
if(!empty($proxy))
{
$this->setProperties(CURLOPT_HTTPPROXYTUNNEL, 1);
$this->setProperties(CURLOPT_PROXY, $proxy);
}
$this->postFile = array();
$this->postData = array();
curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER, true);
}
public function __destruct()
{
curl_close($this->curlHandle);
}
public function httpAuthentication($username, $password)
{
$this->setProperties(CURLOPT_USERPWD, "$username:$password");
$this->setProperties(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
}
public function addHeader($name, $value)
{
$this->setProperties(CURLOPT_HTTPHEADER, array("$name: $value"));
}
public function getLastError()
{
return $this->lastError;
}
private function setProperties($properties, $values)
{
curl_setopt($this->curlHandle, $properties, $values);
}
public function setAccept($format)
{
$curlHttpHeader[] = "Accept: $format";
$this->setProperties(CURLOPT_HTTPHEADER, $curlHttpHeader);
}
public function dontVerifySSLCACert()
{
$this->setProperties(CURLOPT_SSL_VERIFYHOST, 0);
$this->setProperties(CURLOPT_SSL_VERIFYPEER, 0);
}
public function setServerCertificate($serverCertificate)
{
$this->setProperties(CURLOPT_CAINFO, $serverCertificate);
$this->setProperties(CURLOPT_SSL_VERIFYPEER, 0);
}
public function setClientCertificate($clientCertificate, $clientKey, $clientKeyPassword)
{
$this->setProperties(CURLOPT_SSLCERT, $clientCertificate);
$this->setProperties(CURLOPT_SSLKEY, $clientKey);
$this->setProperties(CURLOPT_SSLKEYPASSWD, $clientKeyPassword);
}
public function get($url)
{
$this->setProperties(CURLOPT_URL, $url);
if ($this->postData || $this->postFile) {
$this->curlSetPostData();
}
curl_setopt($this->curlHandle, CURLINFO_HEADER_OUT, true);
$output = curl_exec($this->curlHandle);
//print_r(curl_getinfo($this->curlHandle,CURLINFO_HEADER_OUT));
$this->lastError = curl_error($this->curlHandle);
if ($this->lastError) {
$this->lastError = "Erreur de connexion au serveur : " . $this->lastError;
return false;
}
return $output;
}
public function addPostData($name, $value)
{
if (!isset($this->postData[$name])) {
$this->postData[$name] = array();
}
$this->postData[$name][] = $value;
}
public function setPostDataUrlEncode(array $post_data)
{
$pd = array();
foreach ($post_data as $k => $v) {
$pd[] = "$k=$v";
}
$pd = implode("&", $pd);
$this->setProperties(CURLOPT_POST, true);
$this->setProperties(CURLOPT_POSTFIELDS, $pd);
$this->setProperties(CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
}
public function addPostFile($field, $filePath, $fileName = false, $contentType = "application/octet-stream", $contentTransferEncoding = false)
{
if (!$fileName) {
$fileName = basename($filePath);
}
$this->postFile[$field][$fileName] = $filePath;
$this->postFileProperties[$field][$fileName] = array($contentType, $contentTransferEncoding);
}
private function getBoundary()
{
return '----------------------------' .
substr(sha1('CurlWrapper' . microtime()), 0, 12);
}
private function curlSetPostData()
{
$this->setProperties(CURLOPT_POST, true);
if ($this->isPostDataWithSimilarName()) {
$this->curlSetPostDataWithSimilarFilename();
} else {
$this->curlPostDataStandard();
}
}
private function isPostDataWithSimilarName()
{
$array = array();
foreach ($this->postData as $name => $multipleValue) {
foreach ($multipleValue as $data) {
if (isset($array[$name])) {
return true;
}
$array[$name] = true;
}
}
foreach ($this->postFile as $name => $multipleValue) {
foreach ($multipleValue as $data) {
if (isset($array[$name])) {
return true;
}
$array[$name] = true;
}
}
}
private function curlPostDataStandard()
{
$post = array();
foreach ($this->postData as $name => $multipleValue) {
foreach ($multipleValue as $value) {
$post[$name] = $value;
}
}
foreach ($this->postFile as $name => $multipleValue) {
foreach ($multipleValue as $fileName => $filePath) {
$post[$name] = "@$filePath;filename=$fileName";
}
}
curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, $post);
}
private function curlSetPostDataWithSimilarFilename()
{
$boundary = $this->getBoundary();
$body = array();
foreach ($this->postData as $name => $multipleValue) {
foreach ($multipleValue as $value) {
$body[] = "--$boundary";
$body[] = "Content-Disposition: form-data; name=$name";
$body[] = '';
$body[] = $value;
}
}
foreach ($this->postFile as $name => $multipleValue) {
foreach ($multipleValue as $fileName => $filePath) {
$body[] = "--$boundary";
$body[] = "Content-Disposition: form-data; name=$name; filename=\"$fileName\"";
$body[] = "Content-Type: {$this->postFileProperties[$name][$fileName][0]}";
if ($this->postFileProperties[$name][$fileName][1]) {
$body[] = "Content-Transfer-Encoding: {$this->postFileProperties[$name][$fileName][1]}";
}
$body[] = '';
$body[] = file_get_contents($filePath);
}
}
$body[] = "--$boundary--";
$body[] = '';
$content = join(self::POST_DATA_SEPARATOR, $body);
$curlHttpHeader[] = 'Content-Length: ' . strlen($content);
$curlHttpHeader[] = 'Expect: 100-continue';
$curlHttpHeader[] = "Content-Type: multipart/form-data; boundary=$boundary";
$this->setProperties(CURLOPT_HTTPHEADER, $curlHttpHeader);
$this->setProperties(CURLOPT_POSTFIELDS, $content);
}
public function getHTTPCode()
{
return curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
}
}
#### e- TmsSsoClientBundle/Model/User.php
##### * Attributs
/**
* France Connect identifier
*
* @var string
*/
protected $franceConnectId = null;
/**
* user phone
*
* @var string
*/
protected $phone = null;
/**
* user birthdate
*
* @var string
*/
protected $birthdate = null;
/**
* user birthplace
*
* @var string
*/
protected $birthplace = null;
##### * Récuperation des info utilisateur "setFromResponse"
} else if ($resourceOwner instanceof TmsFranceConnectResourceOwner) {
$this->franceConnectId = $raw['sub'];
$this->username = isset($raw['email']) ? $raw['email'] : null;
$this->gender = isset($raw['gender']) ? (('male' == $raw['gender']) ? 1: 2) : 0;
$this->firstName = isset($raw['given_name']) ? $raw['given_name'] : null;
$this->lastName = isset($raw['family_name']) ? $raw['family_name'] : null;
$this->phone = isset($raw['phone']) ? $raw['phone'] : null;
$this->birthdate = isset($raw['birthdate']) ? $raw['birthdate'] : null;
$this->birthplace = isset($raw['birthplace']) ? $raw['birthplace'] : null;
}
## WebConsumerApp
### 1- Configuration
#### a- WebConsumerApp/app/config/config.yml
tms_france_connect:
type: tms_france_connect
scope: "email profile"
options:
display: popup
base_url: "%dgafp.aipe.france_connect.base_url%"
sup_scope: "%dgafp.aipe.france_connect.sup_scope%"
access_token_url: "%dgafp.aipe.france_connect.access_token_url%"
infos_url: "%dgafp.aipe.france_connect.infos_url%"
certificates: "%dgafp.aipe.france_connect.certificates%"
identity:
selector: tms_web_security.identity.customer
tokens:
dgafp:
client_id: "%dgafp.aipe.france_connect.id%"
client_secret: "%dgafp.aipe.france_connect.secret%"
api_token: "%dgafp.aipe.france_connect.api_token%"
#### b- WebConsumerApp/app/config/parameters.yml
dgafp.aipe.france_connect.id: 1553bc08000a5c65e18a2d8481e8e1b8991f3c253445331141eb4f13e3b89b5b
dgafp.aipe.france_connect.secret: ea1ce2c9d09b2be59e5fae9930de7c542e5b807f18e38dc2e1f40ac6e05397c8
dgafp.aipe.france_connect.base_url: 'https://fcp.integ01.dev-franceconnect.fr/api/v1/'
dgafp.aipe.france_connect.sup_scope: 'profile,email,preferred_username,birthdate,birthplace'
dgafp.aipe.france_connect.access_token_url: 'https://fcp.integ01.dev-franceconnect.fr/api/v1/token'
dgafp.aipe.france_connect.infos_url: 'https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo'
dgafp.aipe.france_connect.certificates: /home/bouraoui/Documents/certificates/certificates.pem
dgafp.aipe.france_connect.api_token: 1g89fhc3opokkggkwckg8gs48k08go840ggs848c0kgwg88ggc
#### c- WebConsumerApp/app/config/routing.yml
france_connect_login:
path: /oauth/login/check-france-connect
#### d- WebConsumerApp/app/config/security.yml
oauth_secured_area:
pattern: ^/
oauth:
resource_owners:
tms: /oauth/login/check-tms
tms_facebook: /oauth/login/check-facebook
tms_google: /oauth/login/check-google
tms_france_connect: /oauth/login/check-france-connect
external: /oauth/login/check-external
check_path: tms_login
login_path: tms_websecurity_connect_login
failure_path: tms_websecurity_connect_login
success_handler: tms_web_portal.listener.login
oauth_user_provider:
service: da_oauth_client.user_provider.memory
logout:
success_handler: tms_web_portal.listener.logout
anonymous: ~
#### e-WebConsumerApp/src/Tms/WebPortalBundle/Resources/themes/dgafp.aipe/views/User/login.html.twig
<a href="{{ host ~ path("hwi_oauth_service_redirect", {service: "tms_france_connect"}) }}" title="Se connecter avec FranceConnect" class="button social france-connect "></a>