diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6b4bbdf78392e390df14a5015d7cf98ad3e07332..6bbc8178a432622a9390eaa3ab7372bbf7b4bcae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,18 @@
 CHANGELOG
 =========
 
+3.2.1
+-----
+- Modifications/améliorations pour faciliter le support d'autres modes d'authentification (ex: unicaen/auth-token). 
+- Le type d'authentification souhaité (local, shib ou cas) peut être spécifié dans l'URL de redirection via le 
+  query param 'authtype'
+- Ajout de la colonne CREATED_AT dans les scripts SQL de création de la table USER (non mappée dans l'entité).  
+- [FIX] Usurpation d'un compte local (db) depuis une authentification shib
+- [FIX] Une chaîne vide doit être considérée comme null dans ShibService::extractShibUserIdValueForDomainFromShibData()
+- [FIX] Nécessité de clés littérales dans la config par domaine de 'shib_user_id_extractor' sinon doublons lors de la 
+  fusion des configs
+
+
 3.2.0
 -----
 - Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification shibboleth
diff --git a/config/module.config.php b/config/module.config.php
index 438010a5697f3cd93533db1777c899e099f98b6b..984369974ea4208a2687ff0cc6787ec8b6ae88ea 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -1,5 +1,7 @@
 <?php
 
+namespace UnicaenAuth;
+
 use UnicaenAuth\Authentication\Adapter\AdapterChainServiceFactory;
 use UnicaenAuth\Authentication\Adapter\Cas;
 use UnicaenAuth\Authentication\Adapter\CasAdapterFactory;
@@ -18,6 +20,7 @@ use UnicaenAuth\Authentication\Storage\LdapFactory;
 use UnicaenAuth\Authentication\Storage\ShibFactory;
 use UnicaenAuth\Authentication\Storage\Usurpation;
 use UnicaenAuth\Authentication\Storage\UsurpationFactory;
+use UnicaenAuth\Controller\AuthController;
 use UnicaenAuth\Controller\AuthControllerFactory;
 use UnicaenAuth\Controller\DroitsControllerFactory;
 use UnicaenAuth\Controller\UtilisateurControllerFactory;
@@ -30,12 +33,14 @@ use UnicaenAuth\Form\ShibLoginForm;
 use UnicaenAuth\Form\ShibLoginFormFactory;
 use UnicaenAuth\Guard\PrivilegeControllerFactory;
 use UnicaenAuth\Guard\PrivilegeRouteFactory;
+use UnicaenAuth\Options\ModuleOptions;
 use UnicaenAuth\ORM\Event\Listeners\HistoriqueListenerFactory;
 use UnicaenAuth\Provider\Rule\PrivilegeRuleProviderFactory;
 use UnicaenAuth\Service\CasService;
 use UnicaenAuth\Service\CasServiceFactory;
 use UnicaenAuth\Service\ShibService;
 use UnicaenAuth\Service\ShibServiceFactory;
+use UnicaenAuth\Service\UserContext;
 use UnicaenAuth\Service\UserContextFactory;
 use UnicaenAuth\Service\UserFactory;
 use UnicaenAuth\Service\UserMapperFactory;
@@ -70,6 +75,15 @@ use Zend\Authentication\AuthenticationService;
 use Zend\ServiceManager\Proxy\LazyServiceFactory;
 
 $settings = [
+    /**
+     * Tous les types d'authentification supportés par le module unicaen/auth.
+     */
+    'auth_types' => [
+        'local', // càd 'ldap' et 'db'
+        'cas',
+        'shib',
+    ],
+
     /**
      * Configuration de l'authentification centralisée (CAS).
      */
@@ -197,19 +211,19 @@ $settings = [
             /*
             // domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
             'unicaen.fr' => [
-                [
+                'supannRefId' => [
                     // nom du 1er attribut recherché
                     'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80'
                     // pattern éventuel pour extraire la partie intéressante
                     'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234'
                 ],
-                [
+                'supannEmpId' => [
                     // nom du 2e attribut recherché, si le 1er est introuvable
                     'name' => 'supannEmpId',
                     // pas de pattern donc valeur brute utilisée
                     'preg_match_pattern' => null,
                 ],
-                [
+                'supannEtuId' => [
                     // nom du 3e attribut recherché, si le 2e est introuvable
                     'name' => 'supannEtuId',
                 ],
@@ -217,10 +231,10 @@ $settings = [
             */
             // config de repli pour tous les autres domaines
             'default' => [
-                [
+                'supannEmpId' => [
                     'name' => 'supannEmpId',
                 ],
-                [
+                'supannEtuId' => [
                     'name' => 'supannEtuId',
                 ],
             ],
@@ -629,21 +643,22 @@ return [
     // in /var/www/sygal/module/Application/src/Application/Controller/UtilisateurController.php on line 34
     'service_manager' => [
         'aliases'            => [
+            'unicaen-auth_module_options' => ModuleOptions::class,
             'zfcuser_login_form' => LoginForm::class,
             'Zend\Authentication\AuthenticationService' => 'zfcuser_auth_service',
             'UnicaenAuth\Privilege\PrivilegeProvider'   => 'UnicaenAuth\Service\Privilege',
             '\UnicaenAuth\Guard\PrivilegeController'    => 'UnicaenAuth\Guard\PrivilegeController',
 
             'unicaen-auth_user_service'               => 'UnicaenAuth\Service\User', // pour la compatibilité
-            'authUserContext'                         => 'UnicaenAuth\Service\UserContext', // pour la compatibilité
-            'AuthUserContext'                         => 'UnicaenAuth\Service\UserContext', // pour la compatibilité
+            'authUserContext'                         => UserContext::class, // pour la compatibilité
+            'AuthUserContext'                         => UserContext::class, // pour la compatibilité
         ],
         'invokables'         => [
             'UnicaenAuth\View\RedirectionStrategy'    => 'UnicaenAuth\View\RedirectionStrategy',
             'UnicaenAuth\Service\CategoriePrivilege'  => 'UnicaenAuth\Service\CategoriePrivilegeService',
         ],
         'factories'          => [
-            'unicaen-auth_module_options'              => 'UnicaenAuth\Options\ModuleOptionsFactory',
+            ModuleOptions::class                       => 'UnicaenAuth\Options\ModuleOptionsFactory',
             'zfcuser_auth_service'                     => 'UnicaenAuth\Authentication\AuthenticationServiceFactory',
             'UnicaenAuth\Authentication\Storage\Chain' => 'UnicaenAuth\Authentication\Storage\ChainServiceFactory',
             'UnicaenAuth\Provider\Identity\Chain'      => 'UnicaenAuth\Provider\Identity\ChainServiceFactory',
@@ -659,7 +674,7 @@ return [
             'zfcuser_redirect_callback'                => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion
             CasService::class                          => CasServiceFactory::class,
             ShibService::class                         => ShibServiceFactory::class,
-            'UnicaenAuth\Service\UserContext'          => UserContextFactory::class,
+            UserContext::class                         => UserContextFactory::class,
             'zfcuser_user_mapper'                      => UserMapperFactory::class,
             'MouchardCompleterAuth'        => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory',
             LocalAdapter::class => LocalAdapterFactory::class,
@@ -708,10 +723,11 @@ return [
     ],
 
     'controllers'   => [
-        'invokables' => [
+        'aliases' => [
+            'UnicaenAuth\Controller\Auth' => AuthController::class,
         ],
         'factories' => [
-            'UnicaenAuth\Controller\Auth'        => AuthControllerFactory::class,
+            AuthController::class => AuthControllerFactory::class,
             'UnicaenAuth\Controller\Utilisateur' => UtilisateurControllerFactory::class,
             'UnicaenAuth\Controller\Droits'      => DroitsControllerFactory::class,
         ],
diff --git a/config/unicaen-auth.local.php.dist b/config/unicaen-auth.local.php.dist
index 1b182e74cd1ef57b1aa4be36331bf8be75f69581..1dba9f252f588e72d2422bd284d7546f68d330a3 100644
--- a/config/unicaen-auth.local.php.dist
+++ b/config/unicaen-auth.local.php.dist
@@ -124,29 +124,29 @@ return [
             'shib_user_id_extractor' => [
                 // domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
 //                'unicaen.fr' => [
-//                    [
+//                    'supannRefId' => [
 //                        // nom du 1er attribut recherché
 //                        'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80'
 //                        // pattern éventuel pour extraire la partie intéressante
 //                        'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234'
 //                    ],
-//                    [
+//                    'supannEmpId' => [
 //                        // nom du 2e attribut recherché
 //                        'name' => 'supannEmpId',
 //                        // pas de pattern donc valeur brute utilisée
 //                        'preg_match_pattern' => null,
 //                    ],
-//                    [
+//                    'supannEtuId' => [
 //                        // nom du 3e attribut recherché
 //                        'name' => 'supannEtuId',
 //                    ],
 //                ],
                 // config de repli pour tous les autres domaines
                 'default' => [
-                    [
+                    'supannEmpId' => [
                         'name' => 'supannEmpId',
                     ],
-                    [
+                    'supannEtuId' => [
                         'name' => 'supannEtuId',
                     ],
                 ],
diff --git a/data/schema.sqlite.sql b/data/schema.sqlite.sql
index 5e8a087ac525dad531be819c7f0320c28125fd3c..6339105798318c15b5488d67a01cb0798d8469ba 100644
--- a/data/schema.sqlite.sql
+++ b/data/schema.sqlite.sql
@@ -6,7 +6,8 @@ CREATE TABLE IF NOT EXISTS user
     display_name VARCHAR(64)  DEFAULT NULL,
     password     VARCHAR(128)                      NOT NULL,
     state        SMALLINT     default 1,
-    last_role_id INTEGER      default null
+    last_role_id INTEGER      default null,
+    created_on   DATE         default current_date not null
 );
 ALTER TABLE user ADD PASSWORD_RESET_TOKEN varchar(256) DEFAULT NULL;
 CREATE UNIQUE INDEX user_unique_username ON user(username);
diff --git a/data/schema_mysql.sql b/data/schema_mysql.sql
index 4b17d9a36ad48bc9b61bb77c651f7465a989cb4a..244b9f7bbf68013d2b4570a868aa23f73f773305 100644
--- a/data/schema_mysql.sql
+++ b/data/schema_mysql.sql
@@ -6,6 +6,7 @@ CREATE TABLE user (
     password VARCHAR(128) NOT NULL,
     state SMALLINT default 1,
     last_role_id INT(11) default null,
+    created_on DATE default now() not null,
   PRIMARY KEY (id),
   UNIQUE INDEX unique_username (username ASC)
 ) ENGINE=InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;
diff --git a/data/schema_oracle.sql b/data/schema_oracle.sql
index 19cea72888b9d66f4f4f9d45dfe712d756d98653..ff894f228f26df55e8448a43feaa002949ec8926 100644
--- a/data/schema_oracle.sql
+++ b/data/schema_oracle.sql
@@ -6,6 +6,7 @@ CREATE TABLE "USER"
 	 "PASSWORD" VARCHAR2(128 CHAR) NOT NULL ENABLE,
 	 "STATE" SMALLINT DEFAULT 1 NOT NULL ENABLE,
      "last_role_id" NUMBER(*,0),
+     created_on DATE default sysdate not null,
 	CONSTRAINT "USER_PK" PRIMARY KEY ("ID"),
 	CONSTRAINT "USER_USERNAME_UN" UNIQUE ("USERNAME"),
     CONSTRAINT "USER_LAST_ROLE_FK" FOREIGN KEY ("last_role_id") REFERENCES USER_ROLE ("ID") ENABLE
@@ -64,6 +65,24 @@ INSERT INTO USER_ROLE_LINKER(user_id, role_id)
 SELECT u.id, r.id FROM "USER" u, user_role r WHERE u.username = 'demo' and r.role_id = 'Standard';
 
 
+CREATE TABLE USER_TOKEN
+(
+    ID                   NUMBER(*, 0)            NOT NULL,
+    USER_ID NUMBER(*,0) NOT NULL ENABLE,
+    TOKEN VARCHAR2(256) NOT NULL,
+    ACTION VARCHAR2(256) NOT NULL,
+    NB_ACTIONS smallint default 0 NOT NULL,
+    NB_ACTIONS_MAX smallint default 1 NOT NULL,
+    created_on DATE default sysdate not null,
+    expired_on DATE not null,
+    last_used_on DATE,
+    CONSTRAINT USER_TOKEN_PK PRIMARY KEY (ID),
+    CONSTRAINT USER_TOKEN_USER_FK UNIQUE (USER_ID)
+);
+CREATE INDEX USER_TOKEN_USER_IDX ON USER_TOKEN (USER_ID);
+CREATE SEQUENCE USER_TOKEN_ID_SEQ;
+
+
 
 /**
  * Privilèges
diff --git a/data/schema_postgresql.sql b/data/schema_postgresql.sql
index 21b51a142c81b81ce4e9b90c877992203d0a8b14..634b9916833e84178a3c62e81e999c6594f614ad 100644
--- a/data/schema_postgresql.sql
+++ b/data/schema_postgresql.sql
@@ -6,6 +6,7 @@ CREATE TABLE "user" (
   password VARCHAR(128) NOT NULL,
   state SMALLINT default 1,
   last_role_id SMALLINT,
+  created_on DATE default current_timestamp not null,
   FOREIGN KEY (last_role_id) REFERENCES user_role (id) ON DELETE SET NULL
 ) ;
 CREATE UNIQUE INDEX user_username_unique ON "user" (username);
diff --git a/src/UnicaenAuth/Authentication/Adapter/AbstractDb.php b/src/UnicaenAuth/Authentication/Adapter/AbstractDb.php
new file mode 100644
index 0000000000000000000000000000000000000000..942b496ca2e948f58f10ccc27bf120b9a7be4f53
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/AbstractDb.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
+use Zend\Authentication\Result as AuthenticationResult;
+use Zend\EventManager\EventInterface;
+use Zend\Session\Container as SessionContainer;
+use ZfcUser\Authentication\Adapter\AdapterChainEvent;
+use ZfcUser\Entity\UserInterface;
+use ZfcUser\Mapper\UserInterface as UserMapperInterface;
+
+/**
+ * Classe abstraite des adpater d'authentification à partir de la base de données.
+ * 
+ * Ajout par rapport à la classe mère : si aucune base de données ou table n'existe,
+ * l'authentification ne plante pas (i.e. renvoit false).
+ *
+ * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
+ */
+abstract class AbstractDb extends AbstractAdapter
+{
+    use ModuleOptionsAwareTrait;
+
+    /**
+     * @var string
+     */
+    protected $type;
+
+    /**
+     * @var AdapterChainEvent
+     */
+    protected $event;
+
+    /**
+     * @var UserMapperInterface
+     */
+    protected $mapper;
+
+    /**
+     * @inheritDoc
+     */
+    public function authenticate(EventInterface $e): bool
+    {
+        // NB: Dans la version 3.0.0 de zf-commons/zfc-user, cette méthode prend un EventInterface.
+        // Mais dans la branche 3.x, c'est un AdapterChainEvent !
+        // Si un jour c'est un AdapterChainEvent qui est attendu, plus besoin de faire $e->getTarget().
+        $this->event = $e->getTarget();
+
+        if ($this->event->getIdentity()) {
+            return true;
+        }
+
+        if ($this->isSatisfied()) {
+            $storage = $this->getStorage()->read();
+            $this->event
+                ->setIdentity($storage['identity'])
+                ->setCode(AuthenticationResult::SUCCESS)
+                ->setMessages(array('Authentication successful.'));
+            return true;
+        }
+
+        $userObject = $this->fetchUserObject();
+        if ($userObject === null) {
+            return false;
+        }
+
+        if ($this->moduleOptions->getEnableUserState()) {
+            // Don't allow user to login if state is not in allowed list
+            if (!in_array($userObject->getState(), $this->moduleOptions->getAllowedLoginStates())) {
+                $this->event
+                    ->setCode(AuthenticationResult::FAILURE_UNCATEGORIZED)
+                    ->setMessages(["Ce compte utilisateur a été désactivé"]);
+                $this->setSatisfied(false);
+                return false;
+            }
+        }
+
+        $result = $this->authenticateUserObject($userObject);
+        if ($result === false) {
+            return false;
+        }
+
+        // regen the id
+        $session = new SessionContainer($this->getStorage()->getNamespace());
+        $session->getManager()->regenerateId();
+
+        // Success!
+        $identity = $this->createSessionIdentity($userObject->getUsername());
+        $this->event->setIdentity($identity);
+        $this->setSatisfied(true);
+        $storage = $this->getStorage()->read();
+        $storage['identity'] = $this->event->getIdentity();
+        $this->getStorage()->write($storage);
+        $this->event
+            ->setCode(AuthenticationResult::SUCCESS)
+            ->setMessages(array('Authentication successful.'));
+
+        return true;
+    }
+
+    /**
+     * @return \ZfcUser\Entity\UserInterface|null
+     */
+    abstract protected function fetchUserObject(): ?UserInterface;
+
+    /**
+     * @param \ZfcUser\Entity\UserInterface $userObject
+     * @return bool
+     */
+    abstract protected function authenticateUserObject(UserInterface $userObject): bool;
+
+    /**
+     * setMapper
+     *
+     * @param UserMapperInterface $mapper
+     * @return self
+     */
+    public function setMapper(UserMapperInterface $mapper): self
+    {
+        $this->mapper = $mapper;
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php b/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php
index 9f710458a25f4b0c94c0462ef216874e552a6baf..f17f58f4761252084d9743bf46ee693c54946279 100644
--- a/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php
+++ b/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php
@@ -1,4 +1,5 @@
 <?php
+
 namespace UnicaenAuth\Authentication\Adapter;
 
 use Interop\Container\ContainerInterface;
@@ -7,19 +8,19 @@ use ZfcUser\Options\ModuleOptions;
 
 class AdapterChainServiceFactory
 {
-    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
+    public function __invoke(ContainerInterface $container, $requestedName, array $options = null): AdapterChain
     {
         $chain = new AdapterChain();
 
         $options = $this->getOptions($container);
         $enabledTypes = array_keys($options->getEnabledAuthTypes()); // types d'auth activés
 
-        //iterate and attach multiple adapters and events if offered
+        // on attache chaque adapter uniquement s'il est activé
         foreach ($options->getAuthAdapters() as $priority => $adapterName) {
             /** @var AbstractAdapter $adapter */
             $adapter = $container->get($adapterName);
             if (in_array($adapter->getType(), $enabledTypes)) {
-                $adapter->attach($chain->getEventManager());
+                $adapter->attach($chain->getEventManager(), $priority);
             }
         }
 
@@ -35,9 +36,9 @@ class AdapterChainServiceFactory
      * set options
      *
      * @param ModuleOptions $options
-     * @return AdapterChainServiceFactory
+     * @return self
      */
-    public function setOptions(ModuleOptions $options)
+    public function setOptions(ModuleOptions $options): self
     {
         $this->options = $options;
         return $this;
@@ -49,7 +50,7 @@ class AdapterChainServiceFactory
      * @param ContainerInterface|null $container (optional) Service Locator
      * @return ModuleOptions $options
      */
-    public function getOptions(ContainerInterface $container = null)
+    public function getOptions(ContainerInterface $container = null): ModuleOptions
     {
         if (!$this->options) {
             if (!$container) {
diff --git a/src/UnicaenAuth/Authentication/Adapter/Cas.php b/src/UnicaenAuth/Authentication/Adapter/Cas.php
index a973c3c011a99c3cdbd092cd000fa91c5c7385d6..6a8afa84875ee7645d789861c2953b12dfecee72 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Cas.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Cas.php
@@ -98,10 +98,6 @@ class Cas extends AbstractAdapter
             return true;
         }
 
-        if (! $this->isEnabled()) {
-            return false;
-        }
-
         error_reporting($oldErrorReporting = error_reporting() & ~E_NOTICE);
 
         $this->getCasClient()->forceAuthentication();
@@ -131,23 +127,6 @@ class Cas extends AbstractAdapter
         return true;
     }
 
-    /**
-     * @return bool
-     */
-    protected function isEnabled(): bool
-    {
-        $config = $this->moduleOptions->getCas();
-
-        if (! $config) {
-            return false;
-        }
-        if (isset($config['enabled'])) {
-            return (bool) $config['enabled'];
-        }
-
-        return true;
-    }
-
     /**
      * @inheritDoc
      */
@@ -155,10 +134,6 @@ class Cas extends AbstractAdapter
     {
         parent::logout($e);
 
-        if (! $this->isEnabled()) {
-            return;
-        }
-
         $storage = $this->getStorage()->read();
         if (! isset($storage['identity'])) {
             return;
@@ -247,9 +222,6 @@ class Cas extends AbstractAdapter
      */
     public function reconfigureRoutesForCasAuth(RouteInterface $router)
     {
-        if (! $this->isEnabled()) {
-            return;
-        }
         if(!$router instanceof RouteStackInterface) {
             return;
         }
diff --git a/src/UnicaenAuth/Authentication/Adapter/Db.php b/src/UnicaenAuth/Authentication/Adapter/Db.php
index 1591bb15d921166ee1a65db0aefde151f9f95956..228d2dd3160ace9230ffc300271058505f205bc0 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Db.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Db.php
@@ -2,15 +2,9 @@
 
 namespace UnicaenAuth\Authentication\Adapter;
 
-use UnicaenAuth\Options\ModuleOptions;
-use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
 use Zend\Authentication\Result as AuthenticationResult;
 use Zend\Crypt\Password\Bcrypt;
-use Zend\EventManager\EventInterface;
-use Zend\Session\Container as SessionContainer;
-use ZfcUser\Authentication\Adapter\AdapterChainEvent;
 use ZfcUser\Entity\UserInterface;
-use ZfcUser\Mapper\UserInterface as UserMapperInterface;
 
 /**
  * Adpater d'authentification à partir de la base de données.
@@ -20,67 +14,29 @@ use ZfcUser\Mapper\UserInterface as UserMapperInterface;
  *
  * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
  */
-class Db extends AbstractAdapter
+class Db extends AbstractDb
 {
-    use ModuleOptionsAwareTrait;
-
     const TYPE = 'db';
 
-    /**
-     * @var string
-     */
     protected $type = self::TYPE;
 
-    /**
-     * @var UserMapperInterface
-     */
-    protected $mapper;
-
     /**
      * @var callable
      */
     protected $credentialPreprocessor;
 
     /**
-     * @var ModuleOptions
-     */
-    protected $options;
-
-    /**
-     * @inheritDoc
+     * @return \ZfcUser\Entity\UserInterface|null
      */
-    public function authenticate(EventInterface $e): bool
+    protected function fetchUserObject(): ?UserInterface
     {
-        // NB: Dans la version 3.0.0 de zf-commons/zfc-user, cette méthode prend un EventInterface.
-        // Mais dans la branche 3.x, c'est un AdapterChainEvent !
-        // Si un jour c'est un AdapterChainEvent qui est attendu, plus besoin de faire $e->getTarget().
-        $event = $e->getTarget(); /* @var $event AdapterChainEvent */
-
-        if ($event->getIdentity()) {
-            return true;
-        }
-
-        if ($this->isSatisfied()) {
-            $storage = $this->getStorage()->read();
-            $event
-                ->setIdentity($storage['identity'])
-                ->setCode(AuthenticationResult::SUCCESS)
-                ->setMessages(array('Authentication successful.'));
-            return true;
-        }
-
-        if (! $this->isEnabled()) {
-            return false;
-        }
+        $identity = $this->event->getRequest()->getPost()->get('identity');
 
-        $identity   = $event->getRequest()->getPost()->get('identity');
-        $credential = $event->getRequest()->getPost()->get('credential');
-        $credential = $this->preProcessCredential($credential);
         /** @var UserInterface|null $userObject */
         $userObject = null;
 
         // Cycle through the configured identity sources and test each
-        $fields = $this->options->getAuthIdentityFields();
+        $fields = $this->moduleOptions->getAuthIdentityFields();
         while (!is_object($userObject) && count($fields) > 0) {
             $mode = array_shift($fields);
             switch ($mode) {
@@ -94,69 +50,45 @@ class Db extends AbstractAdapter
         }
 
         if (!$userObject) {
-            $event
+            $this->event
                 ->setCode(AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND)
-                ->setMessages(array('A record with the supplied identity could not be found.'));
+                ->setMessages([]); // NB: ne pas préciser la cause
             $this->setSatisfied(false);
-            return false;
-        }
 
-        if ($this->options->getEnableUserState()) {
-            // Don't allow user to login if state is not in allowed list
-            if (!in_array($userObject->getState(), $this->options->getAllowedLoginStates())) {
-                $event
-                    ->setCode(AuthenticationResult::FAILURE_UNCATEGORIZED)
-                    ->setMessages(array('A record with the supplied identity is not active.'));
-                $this->setSatisfied(false);
-                return false;
-            }
+            return null;
         }
 
+        return $userObject;
+    }
+
+    /**
+     * @param \ZfcUser\Entity\UserInterface $userObject
+     * @return bool
+     */
+    protected function authenticateUserObject(UserInterface $userObject): bool
+    {
+        $credential = $this->event->getRequest()->getPost()->get('credential');
+        $credential = $this->preProcessCredential($credential);
+
         $bcrypt = new Bcrypt();
-        $bcrypt->setCost($this->options->getPasswordCost());
-        if (!$bcrypt->verify($credential, $userObject->getPassword())) {
-            // Password does not match
-            $event
+        $bcrypt->setCost($this->moduleOptions->getPasswordCost());
+        $ok = $bcrypt->verify($credential, $userObject->getPassword());
+
+        if (!$ok) {
+            $this->event
                 ->setCode(AuthenticationResult::FAILURE_CREDENTIAL_INVALID)
-                ->setMessages(array('Supplied credential is invalid.'));
+                ->setMessages([]); // NB: ne pas préciser la cause
             $this->setSatisfied(false);
+
             return false;
         }
 
-        // regen the id
-        $session = new SessionContainer($this->getStorage()->getNamespace());
-        $session->getManager()->regenerateId();
-
-        // Success!
-        $identity = $this->createSessionIdentity($userObject->getUsername());
-        $event->setIdentity($identity);
         // Update user's password hash if the cost parameter has changed
         $this->updateUserPasswordHash($userObject, $credential, $bcrypt);
-        $this->setSatisfied(true);
-        $storage = $this->getStorage()->read();
-        $storage['identity'] = $event->getIdentity();
-        $this->getStorage()->write($storage);
-        $event
-            ->setCode(AuthenticationResult::SUCCESS)
-            ->setMessages(array('Authentication successful.'));
 
         return true;
     }
 
-    /**
-     * @return bool
-     */
-    protected function isEnabled(): bool
-    {
-        $config = $this->moduleOptions->getDb();
-
-        if (isset($config['enabled'])) {
-            return (bool) $config['enabled'];
-        }
-
-        return false;
-    }
-
     protected function updateUserPasswordHash(UserInterface $userObject, $password, Bcrypt $bcrypt): self
     {
         $hash = explode('$', $userObject->getPassword());
@@ -179,19 +111,6 @@ class Db extends AbstractAdapter
         return $credential;
     }
 
-    /**
-     * setMapper
-     *
-     * @param UserMapperInterface $mapper
-     * @return self
-     */
-    public function setMapper(UserMapperInterface $mapper): self
-    {
-        $this->mapper = $mapper;
-
-        return $this;
-    }
-
     /**
      * Get credentialPreprocessor.
      *
@@ -213,22 +132,4 @@ class Db extends AbstractAdapter
         $this->credentialPreprocessor = $credentialPreprocessor;
         return $this;
     }
-
-    /**
-     * @param \ZfcUser\Options\ModuleOptions $options
-     * @return self
-     */
-    public function setOptions(\ZfcUser\Options\ModuleOptions $options): self
-    {
-        $this->options = $options;
-        return $this;
-    }
-
-    /**
-     * @return ModuleOptions
-     */
-    protected function getOptions(): ModuleOptions
-    {
-        return $this->options;
-    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php b/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php
index 91fef9611f5d8c14ac26fe797092d03abba4d382..ac7619121af708193926ae6aee522118e0b09146 100644
--- a/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php
+++ b/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php
@@ -5,18 +5,15 @@ namespace UnicaenAuth\Authentication\Adapter;
 use Interop\Container\ContainerInterface;
 use UnicaenAuth\Options\ModuleOptions;
 use Zend\Authentication\Storage\Session;
-use Zend\EventManager\EventManagerAwareInterface;
 use ZfcUser\Mapper\UserInterface as UserMapperInterface;
 
 class DbAdapterFactory
 {
     /**
      * @param ContainerInterface $container
-     * @param string $requestedName
-     * @param array|null $options
-     * @return mixed|Cas|Db|Ldap|EventManagerAwareInterface
+     * @return Db
      */
-    public function __invoke(ContainerInterface $container, string $requestedName, array $options = null)
+    public function __invoke(ContainerInterface $container): Db
     {
         /** @var UserMapperInterface $userMapper */
         $userMapper = $container->get('zfcuser_user_mapper');
@@ -30,7 +27,6 @@ class DbAdapterFactory
             $container->get('unicaen-auth_module_options')->toArray());
         $moduleOptions = new ModuleOptions($options);
         $adapter->setModuleOptions($moduleOptions);
-        $adapter->setOptions($moduleOptions);
 
         $substitut = $moduleOptions->getDb()['type'] ?? null;
         if ($substitut !== null) {
diff --git a/src/UnicaenAuth/Authentication/Adapter/Ldap.php b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
index eb39f8e7ba0a9d43eaa45f67994f7e53e907d43f..8bceddcacfa56a8185147021ccb1eed5dff13899 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Ldap.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
@@ -103,10 +103,6 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
             return true;
         }
 
-        if (! $this->isEnabled()) {
-            return false;
-        }
-
         $username   = $event->getRequest()->getPost()->get('identity');
         $credential = $event->getRequest()->getPost()->get('credential');
 
@@ -122,7 +118,7 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
         if (! $success) {
             $event
                 ->setCode(AuthenticationResult::FAILURE)
-                ->setMessages(['LDAP bind failed.']);
+                ->setMessages([/*'LDAP bind failed.'*/]);
             $this->setSatisfied(false);
             return false;
         }
@@ -132,7 +128,7 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
         if (!$ldapPeople) {
             $event
                 ->setCode(AuthenticationResult::FAILURE)
-                ->setMessages(['Authentication failed.']);
+                ->setMessages([/*'Authentication failed.'*/]);
             $this->setSatisfied(false);
             return false;
         }
@@ -158,20 +154,6 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
         return true;
     }
 
-    /**
-     * @return bool
-     */
-    protected function isEnabled(): bool
-    {
-        $config = $this->moduleOptions->getLdap();
-
-        if (isset($config['enabled'])) {
-            return (bool) $config['enabled'];
-        }
-
-        return false;
-    }
-
     /**
      * Extrait le loginUsurpateur et le loginUsurpé si l'identifiant spécifé est de la forme
      * "loginUsurpateur=loginUsurpé".
diff --git a/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php b/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php
index d91764cc2997fd3cd173709609b3fa173f342114..1890a225009e88f43004a90af32253d9521e92f7 100644
--- a/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php
+++ b/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php
@@ -40,10 +40,6 @@ class LocalAdapter extends AbstractAdapter
             return false;
         }
 
-        if (! $this->isEnabled()) {
-            return false;
-        }
-
         $result = $this->getEventManager()->triggerUntil(function ($result) {
             return $result === true || $result instanceof Response;
         }, 'authenticate', $event);
@@ -79,18 +75,4 @@ class LocalAdapter extends AbstractAdapter
             }
         }
     }
-
-    /**
-     * @return bool
-     */
-    protected function isEnabled()
-    {
-        $config = $this->moduleOptions->getLocal();
-
-        if (isset($config['enabled'])) {
-            return (bool) $config['enabled'];
-        }
-
-        return false;
-    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/Shib.php b/src/UnicaenAuth/Authentication/Adapter/Shib.php
index 56cdcaebcfa50f219debfff84f91090744a0debb..4b8952df2b027b994234730d0faeae25e9f3a01f 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Shib.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Shib.php
@@ -96,10 +96,6 @@ class Shib extends AbstractAdapter
             return true;
         }
 
-        if (! $this->isEnabled()) {
-            return false;
-        }
-
         $shibUser = $this->shibService->getAuthenticatedUser();
 
         if ($shibUser === null) {
@@ -135,20 +131,6 @@ class Shib extends AbstractAdapter
         return true;
     }
 
-    /**
-     * @return bool
-     */
-    protected function isEnabled(): bool
-    {
-        $config = $this->moduleOptions->getShib();
-
-        if (isset($config['enabled'])) {
-            return (bool) $config['enabled'];
-        }
-
-        return false;
-    }
-
     /**
      * @inheritDoc
      */
@@ -156,10 +138,6 @@ class Shib extends AbstractAdapter
     {
         parent::logout($e);
 
-        if (! $this->isEnabled()) {
-            return;
-        }
-
         $storage = $this->getStorage()->read();
         if (! isset($storage['identity'])) {
             return;
diff --git a/src/UnicaenAuth/Authentication/Storage/Shib.php b/src/UnicaenAuth/Authentication/Storage/Shib.php
index 4248137accf6dbc6df6bb9cfca53c34973f3c37a..3ee3ac78197025ba716a0d42b90e91d0e3d048c9 100644
--- a/src/UnicaenAuth/Authentication/Storage/Shib.php
+++ b/src/UnicaenAuth/Authentication/Storage/Shib.php
@@ -46,11 +46,10 @@ class Shib extends AbstractStorage
         $sessionIdentity = $this->storage->read();
         $username = $sessionIdentity->getUsername();
 
-//        // L'identité en session doit ressembler à un EPPN.
-//        $looksLikeEppn = strpos($username, '@') !== false;
-//        if (! $looksLikeEppn) {
-//            return null;
-//        }
+        // L'identité en session doit ressembler à un EPPN.
+        if (! ShibUser::isEppn($username)) {
+            return null;
+        }
 
         return $this->shibService->getAuthenticatedUser();
     }
diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php
index 8fd1a6f0c00698ca1abde4f587681d8d69a9471b..ff5a73b40b59dc792473458598206c20730d20fe 100644
--- a/src/UnicaenAuth/Controller/AuthController.php
+++ b/src/UnicaenAuth/Controller/AuthController.php
@@ -14,7 +14,6 @@ use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
 use UnicaenAuth\Service\ShibService;
 use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
 use UnicaenAuth\Service\Traits\UserServiceAwareTrait;
-use Zend\Form\FormInterface;
 use Zend\Http\Request;
 use Zend\Http\Response;
 use Zend\Mvc\Controller\AbstractActionController;
@@ -33,15 +32,26 @@ use ZfcUser\Controller\Plugin\ZfcUserAuthentication;
  */
 class AuthController extends AbstractActionController
 {
-    const TYPES_LOCAL = ['db', 'ldap'];
-    const TYPE_LOCAL = 'local';
+    const AUTH_TYPE_LOCAL = 'local';
+    const AUTH_TYPE_LOCAL_DB = 'db';
+    const AUTH_TYPE_LOCAL_LDAP = 'ldap';
+    const AUTH_TYPES_LOCAL = [self::AUTH_TYPE_LOCAL_DB, self::AUTH_TYPE_LOCAL_LDAP];
+
+    const AUTH_TYPE_TOKEN = 'token';
+
+    const AUTH_TYPE_QUERY_PARAM = 'authtype';
 
     use ShibServiceAwareTrait;
     use UserServiceAwareTrait;
     use ModuleOptionsAwareTrait;
 
     /**
-     * @var FormInterface[] ['type' => FormInterface]
+     * @var string
+     */
+    protected $defaultAuthType = self::AUTH_TYPE_LOCAL_DB;
+
+    /**
+     * @var LoginForm[] ['type' => LoginForm]
      */
     protected $loginFormForType;
 
@@ -64,11 +74,10 @@ class AuthController extends AbstractActionController
      * @param string $type
      * @return LoginForm
      */
-    public function getLoginFormForType(string $type): FormInterface
+    public function getLoginFormForType(string $type): LoginForm
     {
-        if ($type === self::TYPE_LOCAL) {
-            $types = self::TYPES_LOCAL;
-            $type = reset($types);
+        if ($type === self::AUTH_TYPE_LOCAL) {
+            $type = $this->defaultAuthType;
         }
 
         if (! isset($this->loginFormForType[$type])) {
@@ -94,7 +103,7 @@ class AuthController extends AbstractActionController
     /**
      * @var string
      */
-    protected $failedLoginMessage = "Identifiant ou mot de passe incorrect.";
+    protected $failedLoginMessage = "L'authentification a échoué, merci de réessayer.";
 
     /**
      * Login form
@@ -105,28 +114,24 @@ class AuthController extends AbstractActionController
             return $this->redirect()->toRoute($this->moduleOptions->getLoginRedirectRoute());
         }
 
-        $request = $this->getRequest();
-        $originalType = $this->params('type');
-
-        $type = $this->processedType($originalType);
-        if ($type !== $originalType) {
+        $typeFromRoute = $this->params('type');
+        $typeFromRequest = $this->getRequestedAuthenticationType();
+        $type = $this->processedType($typeFromRequest);
+        if ($type !== $typeFromRoute) {
             return $this->redirect()->toRoute(null, ['type' => $type], ['query' => $this->params()->fromQuery()], true);
         }
 
+        $request = $this->getRequest();
         $form = $this->getLoginFormForType($type);
+        $form->initFromRequest($request);
 
         // si le formulaire POSTé ne possède aucun champ identifiant, on va directement à authenticateAction()
         if ($request->isPost() and ! $request->getPost()->get('identity')) {
             return $this->redirect()->toRoute('zfcuser/authenticate', [], ['query' => $this->params()->fromQuery()], true);
         }
 
-        if ($this->moduleOptions->getUseRedirectParameterIfPresent() && $request->getQuery()->get('redirect')) {
-            $redirect = $request->getQuery()->get('redirect');
-        } else {
-            $redirect = false;
-        }
-
-        $queryParams = ['query' => $redirect ? ['redirect' => $redirect] : []];
+        $redirect = $this->getRequestedRedirect();
+        $queryParams = ['query' => ($redirect ? ['redirect' => $redirect] : [])];
         $url = $this->url()->fromRoute(null, [], $queryParams, true);
         $form->setAttribute('action', $url);
 
@@ -155,13 +160,48 @@ class AuthController extends AbstractActionController
         return $this->authenticateAction();
     }
 
+    /**
+     * @return string|null
+     */
+    protected function getRequestedAuthenticationType(): ?string
+    {
+        // si un type est spécifié dans la route, on prend
+        if ($requestedType = $this->params('type')) {
+            return $requestedType;
+        }
+
+        $requestedType = null;
+
+        // un type d'auth peut être demandé dans l'URL de redirection
+        if ($redirect = $this->getRequestedRedirect()) {
+            parse_str(parse_url(urldecode($redirect), PHP_URL_QUERY), $queryParams);
+            if (isset($queryParams[self::AUTH_TYPE_QUERY_PARAM])) {
+                $requestedType = $queryParams[self::AUTH_TYPE_QUERY_PARAM];
+            }
+        }
+        
+        return $requestedType;
+    }
+
+    /**
+     * @return string|null
+     */
+    protected function getRequestedRedirect(): ?string
+    {
+        if (! $this->moduleOptions->getUseRedirectParameterIfPresent()) {
+            return null;
+        }
+
+        return $this->params()->fromQuery('redirect');
+    }
+
     /**
      * @param string|null $type
      * @return string
      */
     private function processedType(string $type = null): string
     {
-        if ($type === self::TYPE_LOCAL) {
+        if ($type === self::AUTH_TYPE_LOCAL) {
             return $type;
         }
 
@@ -173,8 +213,8 @@ class AuthController extends AbstractActionController
         }
 
         // type spécial pour les modes d'authentification nécessitant un formulaire username/password
-        if (in_array($type, self::TYPES_LOCAL)) {
-            $type = self::TYPE_LOCAL;
+        if (in_array($type, self::AUTH_TYPES_LOCAL)) {
+            $type = self::AUTH_TYPE_LOCAL;
         }
 
         return $type;
@@ -205,7 +245,8 @@ class AuthController extends AbstractActionController
         $auth = $this->zfcUserAuthentication()->getAuthService()->authenticate($adapter);
 
         if (!$auth->isValid()) {
-            $this->flashMessenger()->setNamespace('zfcuser-login-form')->addMessage($this->failedLoginMessage);
+            $message = $auth->getMessages()[0] ?? $this->failedLoginMessage;
+            $this->flashMessenger()->setNamespace('zfcuser-login-form')->addMessage($message);
             $adapter->resetAdapters();
             $url = $this->url()->fromRoute(null, [], ['query' => $redirect ? ['redirect' => $redirect] : []], true);
             return $this->redirect()->toUrl($url);
diff --git a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
index 68617a3b1c08c7f0fd2a84a0d40e00a1ac2fa650..46d47487c7fa859013016c17e52b0909b3e5df02 100644
--- a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
+++ b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
@@ -3,6 +3,7 @@
 namespace UnicaenAuth\Entity\Shibboleth;
 
 use UnicaenAuth\Entity\Db\AbstractUser;
+use Webmozart\Assert\Assert;
 use ZfcUser\Entity\UserInterface;
 
 class ShibUser implements UserInterface
@@ -47,6 +48,41 @@ class ShibUser implements UserInterface
      */
     protected $state = 1;
 
+    /**
+     * Teste si une chaîne ressemble à un EPPN.
+     *
+     * @param string $username
+     * @return bool
+     */
+    static public function isEppn(string $username)
+    {
+        if (($pos = strpos($username, '@')) === false) {
+            return false;
+        }
+
+        $domain = substr($username, $pos + 1);
+        if (filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Extrait le domaine de l'EPPN spécifié.
+     *
+     * @param string $eppn
+     * @return string
+     */
+    static public function extractDomainFromEppn(string $eppn)
+    {
+        Assert::true(static::isEppn($eppn), "La chaîne suivante n'est pas un EPPN valide : " . $eppn);
+
+        $parts = explode('@', $eppn);
+
+        return $parts[1];
+    }
+
     /**
      * Retourne la partie domaine DNS de l'EPPN.
      * Retourne par exemple "unicaen.fr" lorsque l'EPPN est "tartempion@unicaen.fr"
@@ -55,9 +91,7 @@ class ShibUser implements UserInterface
      */
     public function getEppnDomain()
     {
-        $parts = explode('@', $this->getEppn());
-
-        return $parts[1];
+        return static::extractDomainFromEppn($this->getEppn());
     }
 
     /**
@@ -75,6 +109,8 @@ class ShibUser implements UserInterface
      */
     public function setEppn($eppn)
     {
+        Assert::true(static::isEppn($eppn), "La chaîne suivante n'est pas un EPPN valide : " . $eppn);
+
         $this->setUsername($eppn);
     }
 
diff --git a/src/UnicaenAuth/Form/LoginForm.php b/src/UnicaenAuth/Form/LoginForm.php
index 1ef8270bf57244a2aee3a2c8697e21c8e3d1bd07..8e0cdd901164feed4c4b845ebb321a164dc99c1f 100644
--- a/src/UnicaenAuth/Form/LoginForm.php
+++ b/src/UnicaenAuth/Form/LoginForm.php
@@ -2,6 +2,7 @@
 
 namespace UnicaenAuth\Form;
 
+use Zend\Http\Request;
 use ZfcUser\Form\Login;
 
 class LoginForm extends Login
@@ -11,6 +12,11 @@ class LoginForm extends Login
      */
     protected $types = ['db', 'ldap'];
 
+    /**
+     * @var bool
+     */
+    protected $hidden = false;
+
     /**
      * @param string[] $types
      * @return self
@@ -28,4 +34,31 @@ class LoginForm extends Login
     {
         return $this->types;
     }
+
+    /**
+     * @param \Zend\Http\Request $request
+     * @return void
+     */
+    public function initFromRequest(Request $request)
+    {
+
+    }
+
+    /**
+     * @return bool
+     */
+    public function isHidden(): bool
+    {
+        return $this->hidden;
+    }
+
+    /**
+     * @param bool $hidden
+     * @return self
+     */
+    public function setHidden(bool $hidden): self
+    {
+        $this->hidden = $hidden;
+        return $this;
+    }
 }
diff --git a/src/UnicaenAuth/Options/ModuleOptions.php b/src/UnicaenAuth/Options/ModuleOptions.php
index ae2daf8d8c00c342a34a8983d7aaaa9e12abef59..dfae29844512e5c54fdcaa8fcf80d31736a1153d 100644
--- a/src/UnicaenAuth/Options/ModuleOptions.php
+++ b/src/UnicaenAuth/Options/ModuleOptions.php
@@ -60,10 +60,41 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      */
     protected $entityManagerName = 'doctrine.entitymanager.orm_default';
 
+    /**
+     * @var array
+     */
+    protected $authTypes;
+
+    /**
+     * @var array|\Traversable|null
+     */
+    protected $originalOptions;
+
+    /**
+     * @inheritDoc
+     */
+    public function __construct($options = null)
+    {
+        parent::__construct($options);
+
+        /** On conserve le tableau de config original, @see __get(). */
+        $this->originalOptions = $options;
+    }
+
+    /**
+     * @param array $authTypes
+     * @return self
+     */
+    public function setAuthTypes(array $authTypes): self
+    {
+        $this->authTypes = $authTypes;
+        return $this;
+    }
+
     /**
      * @return array
      */
-    public function getLocal()
+    public function getLocal(): array
     {
         return $this->local;
     }
@@ -72,7 +103,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * @param array $local
      * @return self
      */
-    public function setLocal(array $local)
+    public function setLocal(array $local): self
     {
         $this->local = $local;
         return $this;
@@ -81,19 +112,19 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     /**
      * @return array
      */
-    public function getDb()
+    public function getDb(): array
     {
         return $this->local['db'];
     }
 
     /**
-     * @param array $db
-     * @return ModuleOptions
+     * @param array $config
+     * @return self
      */
-    public function setDb(array $db)
+    public function setDb(array $config): self
     {
-        $this->db = $db;
-        $this->local['db'] = $db;
+        $this->db = $config;
+        $this->local['db'] = $config;
 
         return $this;
     }
@@ -103,7 +134,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      *
      * @return array
      */
-    public function getLdap()
+    public function getLdap(): array
     {
         return $this->local['ldap'];
     }
@@ -114,7 +145,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * @param array $ldap
      * @return self
      */
-    public function setLdap(array $ldap)
+    public function setLdap(array $ldap): self
     {
         $this->ldap = $ldap;
         $this->local['ldap'] = $ldap;
@@ -123,15 +154,18 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     }
 
     /**
-     * @return array[] Ex: ['db' => ['enabled'=>true, 'type'=>'local'], 'shib' => ['enabled'=>true]]
+     * Configs ordonnées des types d'authentification activés.
+     *
+     * @return array[] Exemple : ['local' => ['enabled'=>true, ...], 'shib' => ['enabled'=>true, ...]]
      */
-    public function getEnabledAuthTypes()
+    public function getEnabledAuthTypes(): array
     {
-        $array = [
-            'local' => $this->getLocal(),
-            'cas' => $this->getCas(),
-            'shib' => $this->getShib(),
-        ];
+        $array = [];
+        foreach ($this->authTypes as $authType) {
+            /** @var array $authTypeConfig */
+            $authTypeConfig = $this->__get($authType);
+            $array[$authType] = $authTypeConfig;
+        }
 
         $array = array_filter($array, function(array $config) {
             return
@@ -149,10 +183,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * set usernames allowed to make usurpation
      *
      * @param array $usurpationAllowedUsernames
-     *
-     * @return ModuleOptions
+     * @return self
      */
-    public function setUsurpationAllowedUsernames(array $usurpationAllowedUsernames = [])
+    public function setUsurpationAllowedUsernames(array $usurpationAllowedUsernames = []): self
     {
         $this->usurpationAllowedUsernames = $usurpationAllowedUsernames;
 
@@ -164,7 +197,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      *
      * @return array
      */
-    public function getUsurpationAllowedUsernames()
+    public function getUsurpationAllowedUsernames(): array
     {
         return $this->usurpationAllowedUsernames;
     }
@@ -174,10 +207,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * de données de l'appli
      *
      * @param bool $flag
-     *
-     * @return ModuleOptions
+     * @return self
      */
-    public function setSaveLdapUserInDatabase($flag = true)
+    public function setSaveLdapUserInDatabase($flag = true): self
     {
         $this->saveLdapUserInDatabase = (bool)$flag;
 
@@ -190,7 +222,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      *
      * @return bool
      */
-    public function getSaveLdapUserInDatabase()
+    public function getSaveLdapUserInDatabase(): bool
     {
         return $this->saveLdapUserInDatabase;
     }
@@ -198,17 +230,16 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     /**
      * @return string
      */
-    public function getLdapUsername()
+    public function getLdapUsername(): string
     {
         return $this->ldapUsername;
     }
 
     /**
      * @param string $ldapUsername
-     *
-     * @return ModuleOptions
+     * @return self
      */
-    public function setLdapUsername($ldapUsername)
+    public function setLdapUsername(string $ldapUsername): self
     {
         $this->ldapUsername = $ldapUsername;
 
@@ -219,10 +250,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * set cas connection params
      *
      * @param array $cas
-     *
-     * @return ModuleOptions
+     * @return self
      */
-    public function setCas(array $cas = [])
+    public function setCas(array $cas = []): self
     {
         $this->cas = $cas;
 
@@ -234,7 +264,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      *
      * @return array
      */
-    public function getCas()
+    public function getCas(): array
     {
         return $this->cas;
     }
@@ -243,10 +273,10 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * set shibboleth connection params
      *
      * @param array $shibboleth
-     * @return ModuleOptions
+     * @return self
      * @deprecated Utiliser setShib()
      */
-    public function setShibboleth(array $shibboleth = [])
+    public function setShibboleth(array $shibboleth = []): self
     {
         return $this->setShib($shibboleth);
     }
@@ -257,7 +287,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * @return array
      * @deprecated Utiliser getShib()
      */
-    public function getShibboleth()
+    public function getShibboleth(): array
     {
         return $this->getShib();
     }
@@ -265,10 +295,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     /**
      * set shibboleth connection params
      *
-     * @param array $shib
-     * @return ModuleOptions
+     * @return self
      */
-    public function setShib(array $shib = [])
+    public function setShib(array $shib = []): self
     {
         $this->shib = $shib;
 
@@ -280,7 +309,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      *
      * @return array
      */
-    public function getShib()
+    public function getShib(): array
     {
         return $this->shib;
     }
@@ -288,20 +317,41 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     /**
      * @return string
      */
-    public function getEntityManagerName()
+    public function getEntityManagerName(): string
     {
         return $this->entityManagerName;
     }
 
     /**
      * @param string $entityManagerName
-     *
-     * @return ModuleOptions
+     * @return self
      */
-    public function setEntityManagerName($entityManagerName)
+    public function setEntityManagerName(string $entityManagerName): self
     {
         $this->entityManagerName = $entityManagerName;
 
         return $this;
     }
+
+    /**
+     * Cette classe hérite de {@see \Zend\Stdlib\AbstractOptions} qui impose d'avoir un getter correspondant à chaque
+     * clé de config fournie. Cela empêche par ex unicaen/auth-token d'ajouter le type d'authentification 'token'
+     * sans ajouter un getToken() à cette classe, ce qui serait une dépendance inversée.
+     *
+     * Donc l'idée est de pouvoir récupérer un bout de config même s'il n'y a pas de getter associé.
+     *
+     * @inheritDoc
+     */
+    public function __get($key)
+    {
+        try {
+            return parent::__get($key);
+        } catch (\BadMethodCallException $e) {
+            if (array_key_exists($key, $this->originalOptions)) {
+                return $this->originalOptions[$key];
+            } else {
+                throw $e;
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Options/ModuleOptionsFactory.php b/src/UnicaenAuth/Options/ModuleOptionsFactory.php
index 5c129849350d72be0c92aa6ba0f4ee43ed77fcc6..582d8519d9b3b2d24aafa297e03e5796b67f9191 100644
--- a/src/UnicaenAuth/Options/ModuleOptionsFactory.php
+++ b/src/UnicaenAuth/Options/ModuleOptionsFactory.php
@@ -12,6 +12,8 @@ use UnicaenApp\Exception\RuntimeException;
  */
 class ModuleOptionsFactory
 {
+    protected $class = ModuleOptions::class;
+
     /**
      * Create service
      *
@@ -21,18 +23,18 @@ class ModuleOptionsFactory
     public function __invoke(ContainerInterface $container)
     {
         $config       = $container->get('Configuration');
-        $moduleConfig = isset($config['unicaen-auth']) ? $config['unicaen-auth'] : [];
+        $moduleConfig = $config['unicaen-auth'] ?? [];
         $moduleConfig = array_merge($config['zfcuser'], $moduleConfig);
 
         $this->validateConfig($moduleConfig);
 
-        return new ModuleOptions($moduleConfig);
+        return new $this->class($moduleConfig);
     }
 
     /**
      * @param array $config
      */
-    private function validateConfig(array $config)
+    protected function validateConfig(array $config)
     {
         //
         // Config authentification shibboleth.
@@ -65,7 +67,6 @@ class ModuleOptionsFactory
         //
         // Config authentification Db.
         //
-        $ldapConfig = [];
         if (array_key_exists($k = 'db', $config)) {
             throw new RuntimeException(
                 "La clé de config 'unicaen-auth.$k' et son contenu doivent être déplacés sous la nouvelle clé 'unicaen-auth.local'"
@@ -75,13 +76,10 @@ class ModuleOptionsFactory
         //
         // Config authentification LDAP.
         //
-        $ldapConfig = [];
         if (array_key_exists($k = 'ldap', $config)) {
             throw new RuntimeException(
                 "La clé de config 'unicaen-auth.$k' et son contenu doivent être déplacés sous la nouvelle clé 'unicaen-auth.local'"
             );
         }
-
-
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php b/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php
index 2d492bb48155e793603e90b1f36df81e5eb98b5f..939378632ae046e97cb62093b8afa0b8698a2cb2 100644
--- a/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php
+++ b/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php
@@ -2,7 +2,6 @@
 
 namespace UnicaenAuth\Options\Traits;
 
-use RuntimeException;
 use UnicaenAuth\Options\ModuleOptions;
 
 /**
diff --git a/src/UnicaenAuth/Service/ShibService.php b/src/UnicaenAuth/Service/ShibService.php
index f4cce5a0d66cab3f046a6e840168722d0d8bd366..ead128108108ec9f092844eef9cd819f414ffe67 100644
--- a/src/UnicaenAuth/Service/ShibService.php
+++ b/src/UnicaenAuth/Service/ShibService.php
@@ -319,6 +319,11 @@ EOS;
      */
     public function activateUsurpation(ShibUser $currentShibUser, AbstractUser $utilisateurUsurpe): self
     {
+        if (! ShibUser::isEppn($utilisateurUsurpe->getUsername())) {
+            // cas d'usurpation d'un compte local (db) depuis une authentification shib
+            return $this;
+        }
+
         $toShibUser = new ShibUser();
         $toShibUser->setEppn($utilisateurUsurpe->getUsername());
         $toShibUser->setId(uniqid()); // peut pas mieux faire pour l'instant
@@ -501,7 +506,7 @@ EOS;
 
         foreach ($config as $array) {
             $name = $array['name'];
-            $value = $this->getValueFromShibData($name, $data);
+            $value = $this->getValueFromShibData($name, $data) ?: null;
             if ($value !== null) {
                 $pregMatchPattern = $array['preg_match_pattern'] ?? null;
                 if ($pregMatchPattern !== null) {
diff --git a/src/UnicaenAuth/View/Helper/ConnectViewHelper.php b/src/UnicaenAuth/View/Helper/ConnectViewHelper.php
index e26adee8854e1bdd85d3a60b25d8ad35dcf1e050..d6fefe50e6f126a407de8f58961db9601882e623 100644
--- a/src/UnicaenAuth/View/Helper/ConnectViewHelper.php
+++ b/src/UnicaenAuth/View/Helper/ConnectViewHelper.php
@@ -2,7 +2,6 @@
 
 namespace UnicaenAuth\View\Helper;
 
-use UnicaenApp\Exception\RuntimeException;
 use Zend\Form\Form;
 use Zend\View\Helper\AbstractHelper;
 use Zend\View\Renderer\PhpRenderer;
@@ -16,52 +15,12 @@ use Zend\View\Renderer\PhpRenderer;
 class ConnectViewHelper extends AbstractHelper
 {
     /**
-     * @param string $type
+     * @param string $type 'local', 'shib', ldap', etc.
      * @param Form $form
      * @return AbstractConnectViewHelper
      */
-    public function __invoke(string $type, Form $form)
+    public function __invoke(string $type, Form $form): AbstractConnectViewHelper
     {
-        switch ($type) {
-            case 'shib':
-                return $this->shibConnect($form);
-            case 'cas':
-                return $this->casConnect($form);
-//            case 'db':
-//                return $this->view->dbConnect($form);
-//            case 'ldap':
-//                return $this->view->ldapConnect($form);
-            case 'local':
-                return $this->localConnect($form);
-        }
-
-        throw new RuntimeException("Aucune aide de vue pour le type '$type'");
-    }
-
-    /**
-     * @param Form $form
-     * @return ShibConnectViewHelper
-     */
-    private function shibConnect(Form $form)
-    {
-        return $this->view->plugin('shibConnect')($form);
-    }
-
-    /**
-     * @param Form $form
-     * @return CasConnectViewHelper
-     */
-    private function casConnect(Form $form)
-    {
-        return $this->view->plugin('casConnect')($form);
-    }
-
-    /**
-     * @param Form $form
-     * @return LocalConnectViewHelper
-     */
-    private function localConnect(Form $form)
-    {
-        return $this->view->plugin('localConnect')($form);
+        return $this->view->plugin($type . 'Connect')($form); // ex: 'localConnect'
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/partial/connect.phtml b/src/UnicaenAuth/View/Helper/partial/connect.phtml
index 6c0a834002f37ba01227541819f8748ead2eaea7..95defbde540db74121903026be43a563abefce50 100644
--- a/src/UnicaenAuth/View/Helper/partial/connect.phtml
+++ b/src/UnicaenAuth/View/Helper/partial/connect.phtml
@@ -27,13 +27,13 @@ use Zend\Form\Form;
 <?php endif ?>
 
 <?php if ($messages = $this->flashMessenger('zfcuser-login-form')): ?>
+<?php foreach ($messages as $message): ?>
 <div class="messenger alert alert-danger ">
     <button type="button" class="close" title="Fermer cette alerte" data-dismiss="alert">×</button>
     <span class="glyphicon glyphicon-warning-sign"></span>
-    <?php foreach ($messages as $message): ?>
         <?php echo $message ?> <br>
-    <?php endforeach ?>
 </div>
+<?php endforeach ?>
 <?php endif ?>
 
 <?php echo $this->render('form.phtml', compact('form', 'type', 'redirect', 'passwordReset')) ?>
diff --git a/view/unicaen-auth/auth/login-tabs.phtml b/view/unicaen-auth/auth/login-tabs.phtml
index 0ad80fce2b0d606dbf84cb9165a454c542289e8b..b22f9a9764402ea06db97b18b86d26799abb60d1 100644
--- a/view/unicaen-auth/auth/login-tabs.phtml
+++ b/view/unicaen-auth/auth/login-tabs.phtml
@@ -10,14 +10,13 @@ use Zend\Form\Form;
  *
  * @var array[] $types Types d'authentification activés, ex: ['db' => ['enabled'=>true, 'type'=>'local'], 'shib' => ['enabled'=>true]]
  * @var string $type Type d'authentification dont il faut afficher le formulaire : ex: 'local', {@see Adapter\Shib::TYPE}
- * @var Form $form Formulaire de connexion
+ * @var LoginForm $form Formulaire de connexion du type spécifié dans la requête
  * @var LoginForm[] $forms Formulaires de connexion possibles
  * @var string $redirect URL demandée nécessitant authentification
  *
  * @method AbstractConnectViewHelper connect()
  */
 
-/** @var Form $form */
 $form->prepare();
 $form->setAttributes([
     'class' => 'form-horizontal',
@@ -27,7 +26,7 @@ $form->setAttributes([
 /** @var AbstractConnectViewHelper[] $helpers */
 $helpers = [];
 foreach ($types as $t => $config) {
-    $helpers[$t] = $this->connect($t, $form);
+    $helpers[$t] = $this->connect($t, $form); /** @see \UnicaenAuth\View\Helper\ConnectViewHelper */
 }
 
 $activeHelper = null;
@@ -40,6 +39,9 @@ $activeHelper = null;
             $activeHelper = $helper;
             $activeClass = 'active';
         } else {
+            if (isset($forms[$key]) && $forms[$key]->isHidden()) {
+                continue;
+            }
             $activeClass = '';
         }
         ?>