# Authentification

# Présentation

Le mécanisme d'authentification utilisé par Anakeen Platform 4 est paramétrable et permet de choisir :

  • l'Authenticator

    L'authenticator implémente le mode d'interaction avec l'utilisateur pour l'obtention de l'identité de l'utilisateur (e.g. formulaire HTML, authentification HTTP Basic, workflow d'authentification OAuth2, etc.)

    L'identité ainsi obtenue par l'authenticator peut être soit :

    • authentifiée : auquel cas il ne sera pas nécessaire de valider celle-ci puisque son authenticité aura été validée par le mécanisme d'authentification lui même (e.g. OAuth2, authentification négociée en amont par le serveur Web, etc.)

    • non-authentifiée : auquel cas il faudra alors valider l'authenticité des identifiants reçus via, au choix, les provider (voir ci-dessous) ou tout autre moyen interne à l'authenticator.

  • le Provider

    L'utilisation d'un provider est optionnelle et ne sert que lorsque l'authenticator n'est pas en mesure d'authentifier l'identité de l'utilisateur.

    Si l'authenticator est en mesure d'authentifier l'identité de la personne qui se connecte (e.g. workflow OAuth2, authentification négociée en amont par le serveur Web, etc.), alors l'utilisation d'un provider ne sera généralement pas nécessaire et toute la mécanique d'authentification pourra être implémentée exclusivement dans l'authenticator.

    Par contre, si l'authenticator ne permet pas d'authentifier l'identité de l'utilisateur, et qu'il ne retourne qu'un couple d'identifiants login/password (e.g. simple formulaire HTML), alors il faudra généralement implémenter un provider afin de vérifier l'identité de l'utilisateur en validant les identifiants login/password fournis par l'authenticator auprès d'un backend d'authentification (e.g. base locale Anakeen Platform, base externe LDAP, etc.)

    Plusieurs provider peuvent être déclarées et il seront alors évalués dans l'ordre de déclaration afin de valider les identifiants fournis. L'évaluation des provider s'arrête au premier provider qui confirme la validité des identifiants fournis. Si aucun provider ne valide les identifiants fournis, alors l'authentification est mise en échec.

Anakeen Platform 4 fournit par défaut les classes Authenticators et Providers suivants :

  • Authenticator

    • \Anakeen\Core\Internal\HtmlAuthenticator : formulaire HTML ;
    • \Anakeen\Core\Internal\BasicAuthenticator : authentification HTTP Basic ;
    • \Anakeen\Core\Internal\OpenAuthenticator : authentification par jeton ;
  • Provider

    • \Anakeen\Core\Internal\Authent\InternalProvider: authentification par défaut sur la base locale d'utilisateurs Anakeen Platform 4.

Un Authenticator peut utiliser un ou plusieurs Provider. Dans ce cas, ils seront utilisés successivement : si le premier Provider autorise l'accès, l'utilisateur est connecté ; sinon on passe au Provider suivant, et ainsi de suite.

Le paramétrage de l'Authenticator, et des Provider à utiliser, s'effectue dans le fichier config/local-dbaccess.php.

# Workflow d'authentification

 Workflow d'authentification

Lorsque l'utilisateur demande une page, un appel est effectué à la méthode checkAuthentication() (1) de l'Authenticator qui vérifie si l'utilisateur est déjà authentifié ou non.

Si l'utilisateur n'est pas authentifié, alors il est redirigé vers la méthode askAuthentication() qui est en charge de dérouter l'exécution vers le code nécessaire à la demande des informations de connexion.

L'utilisateur rentre les informations d'authentification qui sont ensuite validées auprès des Providers configurés via l'appel de la méthode validateCredential() du Provider (4).

Une fois l'utilisateur authentifié, un contrôle d'autorisation est effectué avec d'une part un premier contrôle auprès du Provider via l'appel de la méthode checkAuthorization() de l'Authenticator (2), et d'autre part, un deuxième contrôle d'autorisation final auprès de Anakeen Platform 4 (3).

Si l'authentification réussie, la page est redirigée sur l'url initialement demandée.

Si l'authentification échoue, la page de demande d'authentification est de nouveau affichée.

Notes :

  • Certains workflows d'authentification peuvent être implémentés avec seulement un Authenticator (e.g. authentification OAuth2).

# Paramétrage Authenticator et Provider

L'Authenticator et les Provider utilisés sont paramétrés dans le fichier config/local-dbaccess.php.

Le fichier config/local-dbaccess.php permet de surcharger le fichier de configuration config/dbaccess.php livré par défaut par Anakeen Platform 4.

Exemple de fichier config/dbaccess.php livré par Anakeen Platform 4 :

<?php
/**
 * Postgresql Service Configuration
 * This file is autogenerated - Do not directly write in !
 */
$pgservice_core = "anakeen-platform";

/**
 * Authentication mode : basic / html
 * --------------------------------------------------
 */

$authentMode = 'html';
$authentModeConfig = array(
  'html' => array(),
  'open' => array(),
  'basic' => array()
);

/**
 * Providers : how username / password tuple is validated
 * ------------------------------------------------------
 */
$authentProvidersConfig = [
  "internal" => [
    "class" => "Anakeen\\Core\\Internal\\Authent\\InternalProvider"
  ]
];
$authentProvider = "internal";

$useIndexAsGuest = false;
/*
 ** Include local/override config
 ** -----------------------------
 */
$local_dbaccess = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'local-dbaccess.php';
if (file_exists($local_dbaccess)) {
  include $local_dbaccess;
}

Exemple de fichier config/local-dbaccess.php pour la surcharge du paramétrage :

<?php

$authentMode = 'MyCustomSSO';

$authentModeConfig['MyCustomSSO'] = [
  'class' => 'MyProject\Authent\MyCustomSSOAuthenticator',
  'config' => [
    /*
     * Custom options for MyCustomSSOAuthenticator class
     */
    'clientId' => 'eb72b3b8-7582-4c31-9fda-61aded058a92',
    'clientSecret' => '62e7ee214013dc87135f239be5f748af2c6ed42301cf45059ad35e63dc8d207f',
    'urlAuthorize' => 'https://portal.example.net/oauth/v2/auth',
    'urlAccessToken' => 'https://portal.example.net/oauth/v2/token',
    'debug' => true
  ]
];

Les éléments de configuration utilisables dans ce fichier sont :

  • $authentMode

    Permet de spécifier quel authenticator est utilisé pour demander les informations de connexion à l'utilisateur (login / password la plupart du temps) et lui transmettre le résultat de l'authentification.

    Les différents authenticator disponibles par défaut sont :

    • html (\Anakeen\Core\Internal\HtmlAuthenticator)

      Ce mode fournit une interface HTML pour la saisie des informations de connexion (login + mot de passe), et une session par cookie est ouverte afin de valider les accès ultérieurs.

      C'est l'Authenticator utilisé par défaut.

    • basic (\Anakeen\Core\Internal\BasicAuthenticator)

      Ce mode fournit une authentification HTTP Basic.

      Dans ce mode, Anakeen Platform 4 gère lui même l'authentification au format HTTP Basic, et l'utilisateur rentre son login et son mot de passe dans la boite de dialogue native affichée par le navigateur.

    • open (\Anakeen\Core\Internal\OpenAuthenticator)

      Ce mode fournit une interface basée sur la validation de jetons (voir détails Authentification par jetons.

      Ce mode ne présente pas d'interface pour la saisie du login et du mot de passe, mais se base sur un jeton présent dans l'URI.

      Dans ce mode, le paramètre $authentProvider n'est pas utilisé.

  • $authentProvider

    Ce paramètre spécifie le ou les Provider (séparés, le cas échéant, par des ,) à utiliser pour valider les logins et mots de passe.

    Par défaut, Anakeen Platform 4 fournit les Provider suivants :

    • internal (\Anakeen\Core\Internal\Authent\InternalProvider)

      Ce Provider implémente la validation du login et du mot de passe sur la base locale des utilisateurs de Anakeen Platform 4.

      C'est le Provider utilisé par défaut.

# Configuration des Authenticators

Chaque Authenticator doit définir la classe qui fournit son mécanisme et peut avoir des paramètres pour définir son fonctionnement.

Les paramètres des Authenticator sont spécifiés dans la variable $authentModeConfig.

$authentModeConfig['MyCustomAuthenticator'] = [
  'class' => '\MyProject\Authenty\MyCustomAuthenticator',

  'customOption1' => 'foo',
  'customOption2' => 'bar'
];

# Configuration des Providers

Chaque Provider doit définir la classe qui fournit son mécanisme et peut avoir des paramètres pour définir son fonctionnement.

$authentProvidersConfig['MyCustomProvider'] = [
  'class' => '\MyProject\Authent\MyCustomProvider',

  'customOption1' => 'foo',
  'customOption2' => 'bar'
];

# Sélection dynamique de l'authenticator

Il a été vu que l'authentification passe par un unique Authenticator, couplé à plusieurs Providers.

Dans certains cas il peut-être utile de pouvoir se connecter sur un même contexte via deux Authenticator différents. Cela passe alors par la définition dynamique de $authentMode.

Par exemple dans le cas d'un contexte authentifié par un SSO, il peut-être utile de pouvoir se connecter sur ce système sous le compte admin local (Master Default) sans devoir avoir un compte admin existant sur le SSO.

Pour cela, on peut implémenter un « aiguillage » dans le fichier config/local-dbaccess.php afin de sélectionner le mode d'authentification en fonctions d'éléments de la requête (comme le champ Host: des requêtes HTTP).

Exemple de fichier config/local-dbaccess.php avec « aiguillage » par VirtualHost :

<?php

if ($_SERVER['HTTP_HOST'] == 'admin-ged.example.net') {
  return;
}

$authentMode = 'MyCustomAuthenticator';
$authentModeConfig['MyCustomAuthenticator'] = [
  'class' => '\MyProject\Authent\MyCustomAuthenticator'
];

$authentProvider = 'MyCustomProvider';
$authentProvidersConfig['MyCustomProvider'] = [
  'class' => '\MyProject\Authent\MyCustomProvider'
];

Dans cet exemple, si on se connecte sur l'URL http://admin-ged.example.net, alors c'est le mécanisme d'authentification défini par défaut dans le config/dbaccess.php qui sera appliqué, et dans le cas contraire, on appliquera l'authentification spécifique MyCustomAuthenticator.

De cette manière, les utilisateurs se connectent sur leur URL habituelle (ged.example.net par ex.) avec l'authentification spécifique, et l'administrateur local peut se connecter sur l'hôte admin-ged.example.net avec l'authentification interne.

# Authentification par jetons

L'authentification en mode open permet d'effectuer une requête authentifiée sans fournir l'identifiant ni le mot de passe mais en fournissant un jeton d'authentification.

Cette clef d'authentification est associée à un utilisateur défini et permet d'exécuter des routes sous l'identité de l'utilisateur associé au jeton transmis dans la requête.

# Obtenir un jeton d'authentification

La méthode \Anakeen\Router\AuthenticatorManager::getAuthorizationToken permet de générer un jeton pour un utilisateur donné.

function getAuthorizationToken(
  \Anakeen\Core\Account $userAccount,
  array $routes,
  $expiration = -1,
  bool $oneshot = false,
  $description = ""
) {
}

Cette méthode retourne un jeton sous forme de chaîne de caractères.

  • $userAccount (\Anakeen\Core\Account)

    Compte utilisateur associé au jeton.

  • $routes (array)

    Liste de routes pour lesquelles le jeton sera utilisable.

  • $expiration (int|\Datetime)

    Délai de validité du jeton en secondes. Mettre "-1" (ou false) pour indiquer un délai infini.

  • $oneshot (bool)

    Mettre "true" pour indiquer que le jeton sera valide une seule fois. Le jeton est supprimé une fois consommée.

  • $description (string)

    Texte informatif sur le jeton. Visible lors de l'utilisation de la gestion des jetons dans le centre d'administration

Exemple : Construction d'une URL avec un jeton utilisable une fois pour l'utilisateur courant

$token = \Anakeen\Router\AuthenticatorManager::getAuthorizationToken(
  \Anakeen\Core\ContextManager::getCurrentUser(), // Utilisateur associé au jeton
  [
    [
      "pattern" => "/api/v2/smart-elements/2091.json",
      "methods" => ["GET"]
    ]
  ], // Liste de route(s) d'API autorisée(s) par ce jeton
  3600 * 24, // Durée d'expiration du jeton
  true, // Jeton à usage unique
  'Test jeton dispensable' // Description du jeton
);

$baseUrl = \Anakeen\Core\ContextManager::getParameterValue(\Anakeen\Core\Settings::NsSde, "CORE_URLINDEX");

$url = sprintf('%s/api/v2/xxx?%s=%s', $baseUrl, \Anakeen\Core\Internal\OpenAuthenticator::openGetId, $token);

# Requête pour exécuter une route avec l'authentification

Le jeton doit être indiqué dans l'url avec la variable ank-authorization (constante \Anakeen\Core\Internal\OpenAuthenticator::openGetId). Elle peut aussi être indiquée par une variable dans un formulaire (méthode HTTP POST).

L'url doit contenir la variable "ank-authorization".

Exemple d'URL :

?ank-authorization=e2bb65612c70e7ac78d5ccbfe12aa234

Le jeton peut aussi être indiqué dans le header HTTP :

GET /api/v2/xxx
Autorization: Token e2bb65612c70e7ac78d5ccbfe12aa234

Exemples de requête avec CURL:

  • Requête GET sur un Smart Element en utilisant le token dans les paramètres URL:
curl --request GET 'http://localhost:8080/api/v2/smart-elements/2091.json?ank-authorization=3472e6b54c57d18a2a8fdc82916a007a77b6d205'
  • Requête GET sur un Smart Element en utilisant le HEADER:
curl --request GET --header 'Authorization: Token 3472e6b54c57d18a2a8fdc82916a007a77b6d205' 'http://localhost:8080/api/v2/smart-elements/2091.json'
  • Requête POST en utilisant le token dans les paramètres URL:
curl --request POST 'http://localhost:8080/foo?ank-authorization=3472e6b54c57d18a2a8fdc82916a007a77b6d205'
  • Requête POST en utilisant le HEADER:
curl --request POST --header 'Authorization: Token 3472e6b54c57d18a2a8fdc82916a007a77b6d205' 'http://localhost:8080/foo'

Exemples de requête avec FETCH:

  • Requête GET sur un Smart Element en utilisant le token dans les paramètres URL:
fetch(
  "http://localhost:8080/api/v2/smart-elements/2091.json?ank-authorization=3472e6b54c57d18a2a8fdc82916a007a77b6d205"
)
  .then(function(response) {
    if (response.status !== 200) {
      console.log("Looks like there was a problem. Status Code: " + response.status);
      return;
    }

    response.json().then(function(data) {
      console.log(data);
    });
  })
  .catch(function(err) {
    console.log("Fetch Error :-S", err);
  });
  • Requête GET sur un Smart Element en utilisant le HEADER:
fetch("http://localhost:8080/api/v2/smart-elements/2091.json", {
  method: "get",
  headers: {
    Authorization: "Token 3472e6b54c57d18a2a8fdc82916a007a77b6d205"
  }
})
  .then(function(response) {
    if (response.status !== 200) {
      console.log("Status Code: " + response.status);
      return;
    }

    response.json().then(function(data) {
      console.log(data);
    });
  })
  .catch(function(err) {
    console.log("Fetch Error :-S", err);
  });
  • Requête POST en utilisant le token dans les paramètres URL:
fetch("http://localhost:8080/foo?ank-authorization=3472e6b54c57d18a2a8fdc82916a007a77b6d205", {
  method: "post"
})
  .then(function(response) {
    if (response.status !== 200) {
      console.log("Status Code: " + response.status);
      return;
    }

    response.json().then(function(data) {
      console.log(data);
    });
  })
  .catch(function(err) {
    console.log("Fetch Error :-S", err);
  });
  • Requête POST en utilisant le token dans les headers:
fetch("http://localhost:8080/foo", {
  method: "post",
  headers: {
    Authorization: "Token 3472e6b54c57d18a2a8fdc82916a007a77b6d205"
  }
})
  .then(function(response) {
    if (response.status !== 200) {
      console.log("Status Code: " + response.status);
      return;
    }

    response.json().then(function(data) {
      console.log(data);
    });
  })
  .catch(function(err) {
    console.log("Fetch Error :-S", err);
  });

# Authentification HTTP Basic

L'Authenticator basic ne présente aucune interface pour la saisie du login et du mot de passe, mais se base sur le mécanisme d'authentification HTTP Basic.

La sélection du mode d'authentification basic s'effectue avec la variable GET authtype=basic, qui permet alors d'indiquer de ne pas utiliser l'Authenticator par défaut (html par exemple).

Exemple d'URL avec Authenticator basic :

/xxx?authtype=basic

# Accès avec mot de passe

L'accès peut être effectué en indiquant le login et le mot de passe dans le header HTTP.

GET /xxx
Authorization: Basic am9obi5kb2U6c2VjcmV0

Dans ce cas, le header doit contenir la méthode utilisée (Basic) suivi de la représentation en Base64 du nom de l'utilisateur et du mot de passe séparés par le caractère « : » (deux-points).

En console, l'accès peut être fait avec le programme curl :

curl --user "john.doe:secret" "http://www.example.net/xxx"

# Authentification par session

Le paramètre CORE_SESSION_COOKIE permet de définir l'entête HTTP Set-Cookie envoyé par Anakeen Platform 4. L'authentification par défaut, basée sur un cookie de session, utilise ce paramètre.

Il est possible d'affecter une valeur au paramètre applicatif par défaut CORE_SESSION_COOKIE en suivant les options de la documentation de Set-Cookie.

Par défaut la valeur du paramètre est : {"samesite": "strict", "httponly": true}.

Les options prises en charge dans ce paramètre applicatif sont samesite, httponly, hostonly et secure

Lors d'une mise en production, afin d'avoir une plus grande sécurité, il est conseillé de mettre l'option secure

Exemple de redéfinition du paramètre applicatif :

  ./ank.php --script=setParameter --param=Core::CORE_SESSION_COOKIE --value='{"samesite": "strict", "httponly": true, "secure": true}'