diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f9fe88e8e1c7fa81929e23d1893fed976b6a280..3cbcd155a86ed056e48f94d78f461baadbb184c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,4 +16,15 @@ Première version officielle sous ZF3.
 
 3.0.12 (05/11/2020)
 -------------------
-- Ajout d'une méthode pour pouvoir purger la liste des rôles courante.
\ No newline at end of file
+- Ajout d'une méthode pour pouvoir purger la liste des rôles courante.
+
+3.1.0
+-----
+- Poursuite du typage des authentifications
+    - Chaque adapter peut désormais tester s'il est compétent pour traiter la requête d'authentification.
+    - Création d'un adapter d'authentification comme les autres pour Shib.
+    - Pages de connexion différentes selon le type d'authentification : shib ; db ou ldap ; cas.
+    - Possibilité d'ordonner les formulaires de connexion proposés (config).
+    - Possibilité d'ajouter une description HTML à chaque formulaire de connexion (config).
+- Réparation du mécanisme de redirection vers l'URL demandée avant connexion.
+- Correction du bug de rémanence de l'authentification shibboleth simulée.
diff --git a/Module.php b/Module.php
index 30c0f05b921540b7a88714fbb1076a2eef21ab7f..9a44a439b974fa7e03e64884c8e7873c9f5ce8bb 100644
--- a/Module.php
+++ b/Module.php
@@ -2,16 +2,10 @@
 
 namespace UnicaenAuth;
 
-use UnicaenAuth\Authentication\Adapter\Cas as CasAdapter;
-use UnicaenAuth\Options\ModuleOptions;
-use UnicaenAuth\Service\ShibService;
 use Zend\EventManager\EventInterface;
 use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
 use Zend\ModuleManager\Feature\ConfigProviderInterface;
 use Zend\ModuleManager\Feature\ServiceProviderInterface;
-use Zend\ModuleManager\ModuleManager;
-use Zend\ServiceManager\ServiceLocatorInterface;
-use Zend\View\Helper\Navigation;
 use ZfcUser\Form\Login;
 use ZfcUser\Form\LoginFilter;
 
@@ -22,11 +16,6 @@ use ZfcUser\Form\LoginFilter;
  */
 class Module implements AutoloaderProviderInterface, ConfigProviderInterface, ServiceProviderInterface
 {
-    /**
-     * @var ModuleOptions
-     */
-    private $options;
-
     /**
      * @return array
      * @see ConfigProviderInterface
@@ -64,41 +53,7 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se
      */
     public function onBootstrap(EventInterface $e)
     {
-        /* @var \Zend\Mvc\MvcEvent $e */
-        $application = $e->getApplication();
-        /* @var $services \Zend\ServiceManager\ServiceManager */
-        $services = $application->getServiceManager();
-
-        /* @var $options ModuleOptions */
-        $this->options = $services->get('unicaen-auth_module_options');
-
-        $this->reconfigureRoutesForAuth($services);
-    }
-
-    /**
-     * @param ServiceLocatorInterface $sl
-     */
-    private function reconfigureRoutesForAuth(ServiceLocatorInterface $sl)
-    {
-        /* @var $router \Zend\Router\Http\TreeRouteStack */
-        $router = $sl->get('router');
-
-        // si l'auth CAS est activée, modif de la route de connexion pour zapper le formulaire d'auth maison.
-        $isCasEnable = (bool) $this->options->getCas();
-        if ($isCasEnable && php_sapi_name() !== 'cli') {
-            /** @var CasAdapter $casAdapter */
-            $casAdapter = $sl->get('UnicaenAuth\Authentication\Adapter\Cas');
-            $casAdapter->reconfigureRoutesForCasAuth($router);
-        }
 
-        // si l'auth Shibboleth est activée, modif de la route de déconnexion pour réaliser la déconnexion Shibboleth.
-        $shibOptions = $this->options->getShibboleth();
-        $isShibEnable = array_key_exists('enable', $shibOptions) && (bool) $shibOptions['enable'];
-        if ($isShibEnable && php_sapi_name() !== 'cli') {
-            /** @var ShibService $shibService */
-            $shibService = $sl->get(ShibService::class);
-            $shibService->reconfigureRoutesForShibAuth($router);
-        }
     }
 
     /**
diff --git a/autoload_classmap.php b/autoload_classmap.php
deleted file mode 100644
index 77c1f294ab9baeb932af642bc677fdf64e79b4f5..0000000000000000000000000000000000000000
--- a/autoload_classmap.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-// Generated by ZF2's ./bin/classmap_generator.php
-return array(
-    'UnicaenAuth\Module'                                                => __DIR__ . '/Module.php',
-    'UnicaenAuth\Guard\PrivilegeController'                             => __DIR__ . '/src/UnicaenAuth/Guard/PrivilegeController.php',
-    'UnicaenAuth\Options\AuthenticationOptionsInterface'                => __DIR__ . '/src/UnicaenAuth/Options/AuthenticationOptionsInterface.php',
-    'UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait'                => __DIR__ . '/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php',
-    'UnicaenAuth\Options\ModuleOptionsFactory'                          => __DIR__ . '/src/UnicaenAuth/Options/ModuleOptionsFactory.php',
-    'UnicaenAuth\Options\ModuleOptions'                                 => __DIR__ . '/src/UnicaenAuth/Options/ModuleOptions.php',
-    'UnicaenAuth\Entity\Db\CategoriePrivilege'                          => __DIR__ . '/src/UnicaenAuth/Entity/Db/CategoriePrivilege.php',
-    'UnicaenAuth\Entity\Db\User'                                        => __DIR__ . '/src/UnicaenAuth/Entity/Db/User.php',
-    'UnicaenAuth\Entity\Db\Privilege'                                   => __DIR__ . '/src/UnicaenAuth/Entity/Db/Privilege.php',
-    'UnicaenAuth\Entity\Db\Role'                                        => __DIR__ . '/src/UnicaenAuth/Entity/Db/Role.php',
-    'UnicaenAuth\Entity\Db\AbstractUser'                                => __DIR__ . '/src/UnicaenAuth/Entity/Db/AbstractUser.php',
-    'UnicaenAuth\Entity\Ldap\People'                                    => __DIR__ . '/src/UnicaenAuth/Entity/Ldap/People.php',
-    'UnicaenAuth\Service\LdapUserAwareInterface'                        => __DIR__ . '/src/UnicaenAuth/Service/LdapUserAwareInterface.php',
-    'UnicaenAuth\Service\AuthorizeService'                              => __DIR__ . '/src/UnicaenAuth/Service/AuthorizeService.php',
-    'UnicaenAuth\Service\DbUserAwareInterface'                          => __DIR__ . '/src/UnicaenAuth/Service/DbUserAwareInterface.php',
-    'UnicaenAuth\Service\User'                                          => __DIR__ . '/src/UnicaenAuth/Service/User.php',
-    'UnicaenAuth\Service\Traits\UserContextServiceAwareTrait'           => __DIR__ . '/src/UnicaenAuth/Service/Traits/UserContextServiceAwareTrait.php',
-    'UnicaenAuth\Service\Traits\RoleServiceAwareTrait'                  => __DIR__ . '/src/UnicaenAuth/Service/Traits/RoleServiceAwareTrait.php',
-    'UnicaenAuth\Service\Traits\CategoriePrivilegeServiceAwareTrait'    => __DIR__ . '/src/UnicaenAuth/Service/Traits/CategoriePrivilegeAwareTrait.php',
-    'UnicaenAuth\Service\Traits\PrivilegeServiceAwareTrait'             => __DIR__ . '/src/UnicaenAuth/Service/Traits/PrivilegeServiceAwareTrait.php',
-    'UnicaenAuth\Service\UserAwareInitializer'                          => __DIR__ . '/src/UnicaenAuth/Service/UserAwareInitializer.php',
-    'UnicaenAuth\Service\UserContext'                                   => __DIR__ . '/src/UnicaenAuth/Service/UserContext.php',
-    'UnicaenAuth\Service\PrivilegeService'                              => __DIR__ . '/src/UnicaenAuth/Service/PrivilegeService.php',
-    'UnicaenAuth\Service\RoleService'                                   => __DIR__ . '/src/UnicaenAuth/Service/RoleService.php',
-    'UnicaenAuth\Service\CategoriePrivilegeService'                     => __DIR__ . '/src/UnicaenAuth/Service/CategoriePrivilegeService.php',
-    'UnicaenAuth\Service\AbstractService'                               => __DIR__ . '/src/UnicaenAuth/Service/AbstractService.php',
-    'UnicaenAuth\Service\AuthorizeServiceFactory'                       => __DIR__ . '/src/UnicaenAuth/Service/AuthorizeServiceFactory.php',
-    'UnicaenAuth\Authentication\AuthenticationServiceFactory'           => __DIR__ . '/src/UnicaenAuth/Authentication/AuthenticationServiceFactory.php',
-    'UnicaenAuth\Authentication\Storage\Ldap'                           => __DIR__ . '/src/UnicaenAuth/Authentication/Storage/Ldap.php',
-    'UnicaenAuth\Authentication\Storage\Db'                             => __DIR__ . '/src/UnicaenAuth/Authentication/Storage/Db.php',
-    'UnicaenAuth\Authentication\Storage\Chain'                          => __DIR__ . '/src/UnicaenAuth/Authentication/Storage/Chain.php',
-    'UnicaenAuth\Authentication\Storage\ChainableStorage'               => __DIR__ . '/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php',
-    'UnicaenAuth\Authentication\Storage\ChainServiceFactory'            => __DIR__ . '/src/UnicaenAuth/Authentication/Storage/ChainServiceFactory.php',
-    'UnicaenAuth\Authentication\Storage\ChainEvent'                     => __DIR__ . '/src/UnicaenAuth/Authentication/Storage/ChainEvent.php',
-    'UnicaenAuth\Authentication\Adapter\Ldap'                           => __DIR__ . '/src/UnicaenAuth/Authentication/Adapter/Ldap.php',
-    'UnicaenAuth\Authentication\Adapter\Db'                             => __DIR__ . '/src/UnicaenAuth/Authentication/Adapter/Db.php',
-    'UnicaenAuth\Authentication\Adapter\AbstractFactory'                => __DIR__ . '/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php',
-    'UnicaenAuth\Authentication\Adapter\Cas'                            => __DIR__ . '/src/UnicaenAuth/Authentication/Adapter/Cas.php',
-    'UnicaenAuth\Assertion\AbstractAssertion'                           => __DIR__ . '/src/UnicaenAuth/Assertion/AbstractAssertion.php',
-    'UnicaenAuth\Acl\NamedRole'                                         => __DIR__ . '/src/UnicaenAuth/Acl/NamedRole.php',
-    'UnicaenAuth\View\RedirectionStrategy'                              => __DIR__ . '/src/UnicaenAuth/View/RedirectionStrategy.php',
-    'UnicaenAuth\View\Helper\AppConnection'                             => __DIR__ . '/src/UnicaenAuth/View/Helper/AppConnection.php',
-    'UnicaenAuth\View\Helper\UserProfileSelectRadioItem'                => __DIR__ . '/src/UnicaenAuth/View/Helper/UserProfileSelectRadioItem.php',
-    'UnicaenAuth\View\Helper\UserProfileFactory'                        => __DIR__ . '/src/UnicaenAuth/View/Helper/UserProfileFactory.php',
-    'UnicaenAuth\View\Helper\UserStatus'                                => __DIR__ . '/src/UnicaenAuth/View/Helper/UserStatus.php',
-    'UnicaenAuth\View\Helper\UserStatusFactory'                         => __DIR__ . '/src/UnicaenAuth/View/Helper/UserStatusFactory.php',
-    'UnicaenAuth\View\Helper\UserAbstract'                              => __DIR__ . '/src/UnicaenAuth/View/Helper/UserAbstract.php',
-    'UnicaenAuth\View\Helper\UserProfileSelect'                         => __DIR__ . '/src/UnicaenAuth/View/Helper/UserProfileSelect.php',
-    'UnicaenAuth\View\Helper\UserConnectionFactory'                     => __DIR__ . '/src/UnicaenAuth/View/Helper/UserConnectionFactory.php',
-    'UnicaenAuth\View\Helper\UserInfoFactory'                           => __DIR__ . '/src/UnicaenAuth/View/Helper/UserInfoFactory.php',
-    'UnicaenAuth\View\Helper\UserInfo'                                  => __DIR__ . '/src/UnicaenAuth/View/Helper/UserInfo.php',
-    'UnicaenAuth\View\Helper\UserCurrentFactory'                        => __DIR__ . '/src/UnicaenAuth/View/Helper/UserCurrentFactory.php',
-    'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory'         => __DIR__ . '/src/UnicaenAuth/View/Helper/UserProfileSelectRadioItemFactory.php',
-    'UnicaenAuth\View\Helper\UserProfileSelectFactory'                  => __DIR__ . '/src/UnicaenAuth/View/Helper/UserProfileSelectFactory.php',
-    'UnicaenAuth\View\Helper\UserProfile'                               => __DIR__ . '/src/UnicaenAuth/View/Helper/UserProfile.php',
-    'UnicaenAuth\View\Helper\UserCurrent'                               => __DIR__ . '/src/UnicaenAuth/View/Helper/UserCurrent.php',
-    'UnicaenAuth\View\Helper\UserConnection'                            => __DIR__ . '/src/UnicaenAuth/View/Helper/UserConnection.php',
-    'UnicaenAuth\Controller\DroitsController'                           => __DIR__ . '/src/UnicaenAuth/Controller/DroitsController.php',
-    'UnicaenAuth\Controller\UtilisateurController'                      => __DIR__ . '/src/UnicaenAuth/Controller/UtilisateurController.php',
-    'UnicaenAuth\Provider\Role\ConfigServiceFactory'                    => __DIR__ . '/src/UnicaenAuth/Provider/Role/ConfigServiceFactory.php',
-    'UnicaenAuth\Provider\Role\Config'                                  => __DIR__ . '/src/UnicaenAuth/Provider/Role/Config.php',
-    'UnicaenAuth\Provider\Role\UsernameServiceFactory'                  => __DIR__ . '/src/UnicaenAuth/Provider/Role/UsernameServiceFactory.php',
-    'UnicaenAuth\Provider\Role\DbRole'                                  => __DIR__ . '/src/UnicaenAuth/Provider/Role/DbRole.php',
-    'UnicaenAuth\Provider\Role\DbRoleServiceFactory'                    => __DIR__ . '/src/UnicaenAuth/Provider/Role/DbRoleServiceFactory.php',
-    'UnicaenAuth\Provider\Role\Username'                                => __DIR__ . '/src/UnicaenAuth/Provider/Role/Username.php',
-    'UnicaenAuth\Provider\Privilege\PrivilegeProviderAwareTrait'        => __DIR__ . '/src/UnicaenAuth/Provider/Privilege/PrivilegeProviderAwareTrait.php',
-    'UnicaenAuth\Provider\Privilege\PrivilegeProviderInterface'         => __DIR__ . '/src/UnicaenAuth/Provider/Privilege/PrivilegeProviderInterface.php',
-    'UnicaenAuth\Provider\Privilege\Privileges'                         => __DIR__ . '/src/UnicaenAuth/Provider/Privilege/Privileges.php',
-    'UnicaenAuth\Provider\Rule\PrivilegeRuleProvider'                   => __DIR__ . '/src/UnicaenAuth/Provider/Rule/PrivilegeRuleProvider.php',
-    'UnicaenAuth\Provider\Identity\DbServiceFactory'                    => __DIR__ . '/src/UnicaenAuth/Provider/Identity/DbServiceFactory.php',
-    'UnicaenAuth\Provider\Identity\Basic'                               => __DIR__ . '/src/UnicaenAuth/Provider/Identity/Basic.php',
-    'UnicaenAuth\Provider\Identity\Ldap'                                => __DIR__ . '/src/UnicaenAuth/Provider/Identity/Ldap.php',
-    'UnicaenAuth\Provider\Identity\Db'                                  => __DIR__ . '/src/UnicaenAuth/Provider/Identity/Db.php',
-    'UnicaenAuth\Provider\Identity\Chain'                               => __DIR__ . '/src/UnicaenAuth/Provider/Identity/Chain.php',
-    'UnicaenAuth\Provider\Identity\ChainableProvider'                   => __DIR__ . '/src/UnicaenAuth/Provider/Identity/ChainableProvider.php',
-    'UnicaenAuth\Provider\Identity\LdapServiceFactory'                  => __DIR__ . '/src/UnicaenAuth/Provider/Identity/LdapServiceFactory.php',
-    'UnicaenAuth\Provider\Identity\BasicServiceFactory'                 => __DIR__ . '/src/UnicaenAuth/Provider/Identity/BasicServiceFactory.php',
-    'UnicaenAuth\Provider\Identity\ChainServiceFactory'                 => __DIR__ . '/src/UnicaenAuth/Provider/Identity/ChainServiceFactory.php',
-    'UnicaenAuth\Provider\Identity\ChainEvent'                          => __DIR__ . '/src/UnicaenAuth/Provider/Identity/ChainEvent.php',
-    'UnicaenAuth\Event\UserAuthenticatedEvent'                          => __DIR__ . '/src/UnicaenAuth/Event/UserAuthenticatedEvent.php',
-    'UnicaenAuth\Event\Listener\AuthenticatedUserSavedAbstractListener' => __DIR__ . '/src/UnicaenAuth/Event/Listener/AuthenticatedUserSavedAbstractListener.php',
-    'UnicaenAuth\Form\Droits\Traits\RoleFormAwareTrait'                 => __DIR__ . '/src/UnicaenAuth/Form/Droits/Traits/RoleFormAwareTrait.php',
-    'UnicaenAuth\Form\Droits\RoleForm'                                  => __DIR__ . '/src/UnicaenAuth/Form/Droits/RoleForm.php',
-    'RoleFormHydrator'                                                  => __DIR__ . '/src/UnicaenAuth/Form/Droits/RoleForm.php',
-);
diff --git a/config/module.config.php b/config/module.config.php
index faea79ffc40461e929fef73295e94cf7608aa3a8..f8c8d10a406d2c07bd2cbde9f20e64e827d532a3 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -1,21 +1,37 @@
 <?php
 
+use UnicaenAuth\Authentication\Adapter\AdapterChainServiceFactory;
+use UnicaenAuth\Authentication\Adapter\CasAdapterFactory;
+use UnicaenAuth\Authentication\Adapter\DbAdapterFactory;
+use UnicaenAuth\Authentication\Adapter\LdapAdapterFactory;
+use UnicaenAuth\Authentication\Adapter\ShibAdapterFactory;
 use UnicaenAuth\Authentication\Storage\DbFactory;
 use UnicaenAuth\Authentication\Storage\LdapFactory;
 use UnicaenAuth\Authentication\Storage\ShibFactory;
 use UnicaenAuth\Controller\AuthControllerFactory;
 use UnicaenAuth\Controller\DroitsControllerFactory;
 use UnicaenAuth\Controller\UtilisateurControllerFactory;
+use UnicaenAuth\Form\CasLoginForm;
+use UnicaenAuth\Form\CasLoginFormFactory;
 use UnicaenAuth\Form\Droits\RoleFormFactory;
+use UnicaenAuth\Form\ShibLoginForm;
+use UnicaenAuth\Form\ShibLoginFormFactory;
 use UnicaenAuth\Guard\PrivilegeControllerFactory;
 use UnicaenAuth\Guard\PrivilegeRouteFactory;
 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\UserContextFactory;
 use UnicaenAuth\Service\UserFactory;
 use UnicaenAuth\Service\UserMapperFactory;
+use UnicaenAuth\View\Helper\CasConnectViewHelper;
+use UnicaenAuth\View\Helper\CasConnectViewHelperFactory;
+use UnicaenAuth\View\Helper\ConnectViewHelper;
+use UnicaenAuth\View\Helper\DbConnectViewHelper;
+use UnicaenAuth\View\Helper\DbConnectViewHelperFactory;
 use UnicaenAuth\View\Helper\LdapConnectViewHelper;
 use UnicaenAuth\View\Helper\LdapConnectViewHelperFactory;
 use UnicaenAuth\View\Helper\LocalConnectViewHelper;
@@ -42,25 +58,120 @@ use Zend\Authentication\AuthenticationService;
 use Zend\ServiceManager\Proxy\LazyServiceFactory;
 
 $settings = [
-
     /**
-     * Configuration de l'authentification locale.
+     * Configuration de l'authentification via la fédération d'identité (Shibboleth).
      */
-    'local' => [
+    'shib' => [
         /**
-         * Possibilité ou non de s'authentifier à l'aide d'un compte local.
+         * Ordre d'affichage du formulaire de connexion.
          */
-        'enabled' => true,
+        'order' => 1,
+
+        /**
+         * Activation ou non de ce mode d'authentification.
+         */
+        'enabled' => false,
+
+        /**
+         * Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire de connexion.
+         */
+        'description' => "Cliquez sur le bouton ci-dessous pour accéder à l'authentification via la fédération d'identité.",
+
+        /**
+         * URL de déconnexion.
+         */
+        //'logout_url' => '/Shibboleth.sso/Logout?return=', // NB: '?return=' semble obligatoire!
+
+        /*
+        'simulate' => [
+            'eppn'        => 'login@domain.fr',
+            'supannEmpId' => '00012345',
+        ],
+        'aliases' => [
+            'eppn'                   => 'HTTP_EPPN',
+            'mail'                   => 'HTTP_MAIL',
+            'eduPersonPrincipalName' => 'HTTP_EPPN',
+            'supannEtuId'            => 'HTTP_SUPANNETUID',
+            'supannEmpId'            => 'HTTP_SUPANNEMPID',
+            'supannCivilite'         => 'HTTP_SUPANNCIVILITE',
+            'displayName'            => 'HTTP_DISPLAYNAME',
+            'sn'                     => 'HTTP_SN',
+            'givenName'              => 'HTTP_GIVENNAME',
+        ],
+        /*
+        'required_attributes' => [
+            'eppn',
+            'mail',
+            'eduPersonPrincipalName',
+            'supannCivilite',
+            'displayName',
+            'sn|surname', // i.e. 'sn' ou 'surname'
+            'givenName',
+            'supannEtuId|supannEmpId',
+        ],
+        */
     ],
 
     /**
-     * Configuration de l'authentification LDAP.
+     * Configuration de l'authentification LDAP (compte établissement).
      */
     'ldap' => [
+        'order' => 2,
+        'enabled' => true,
+        'description' => "Utilisez ce formulaire pour vous connecter avec votre compte numérique établissement.",
+
         /**
-         * Possibilité ou non de s'authentifier via l'annuaire LDAP.
+         * Type de substitution.
+         * Permet de "fusionner" les types d'authentification locale (db) et établissement (ldap) et donc leurs
+         * formulaires de connexion respectifs.
          */
-        'enabled' => true,
+        'type' => 'local',
+    ],
+
+    /**
+     * Configuration de l'authentification locale (compte propre à l'appli).
+     */
+    'db' => [
+        'order' => 3,
+        'enabled' => false,
+
+        /**
+         * Type de substitution.
+         * Permet de "grouper" les types d'authentification locale (db) et établissement (ldap) sous un même
+         * formulaire de connexion.
+         */
+        'type' => 'local',
+
+        /**
+         * Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire d'authentification.
+         * NB: si la valeur de 'order' pour le type 'db' est supérieure à celle pour le type 'ldap',
+         * c'est cette description qui sera visible.
+         */
+        'description' => "Utilisez ce formulaire si vous possédez un compte local propre à l'application.",
+    ],
+
+    /**
+     * Configuration de l'authentification centralisée (CAS).
+     */
+    'cas' => [
+        'order' => 4,
+        'enabled' => false,
+        'description' => "Cliquez sur le bouton ci-dessous pour accéder à l'authentification centralisée.",
+
+        /**
+         * Infos de connexion au serveur CAS.
+         */
+        'connection' => [
+            'default' => [
+                'params' => [
+                    'hostname' => 'host.domain.fr',
+                    'port'     => 443,
+                    'version'  => "2.0",
+                    'uri'      => "",
+                    'debug'    => false,
+                ],
+            ],
+        ]
     ],
 
     /**
@@ -129,9 +240,10 @@ return [
          * Accepted values: array containing services that implement 'ZfcUser\Authentication\Adapter\ChainableAdapter'
          */
         'auth_adapters'           => [
-            300 => 'UnicaenAuth\Authentication\Adapter\Ldap', // notifié en 1er
-            200 => 'UnicaenAuth\Authentication\Adapter\Db',   //         ensuite (si échec d'authentification Ldap)
-            100 => 'UnicaenAuth\Authentication\Adapter\Cas',  //         ensuite (si échec d'authentification Db)
+            300 => 'UnicaenAuth\Authentication\Adapter\Ldap',
+            200 => 'UnicaenAuth\Authentication\Adapter\Db',
+            100 => 'UnicaenAuth\Authentication\Adapter\Cas',
+            50 =>  'UnicaenAuth\Authentication\Adapter\Shib',
         ],
 
         // telling ZfcUser to use our own class
@@ -184,6 +296,9 @@ return [
                 ['controller' => 'UnicaenApp\Controller\Application', 'action' => 'refresh-session', 'roles' => 'guest'],
                 ['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'selectionner-profil', 'roles' => 'guest'],
 
+                ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'login', 'roles' => 'guest'],
+                ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'authenticate', 'roles' => 'guest'],
+                ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'logout', 'roles' => 'guest'],
                 ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'shibboleth', 'roles' => 'guest'],
                 ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'requestPasswordReset', 'roles' => 'guest'],
                 ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'changePassword', 'roles' => 'guest'],
@@ -287,21 +402,31 @@ return [
                 'may_terminate' => true,
                 'child_routes'  => [
                     'login'    => [
-                        'type'    => 'Literal',
+                        'type'    => 'Segment',
                         'options' => [
-                            'route'    => '/connexion',
+                            'route'    => '/connexion[/:type]',
                             'defaults' => [
-                                'controller' => 'zfcuser',
+                                'controller' => 'UnicaenAuth\Controller\Auth', // remplace 'zfcuser'
                                 'action'     => 'login',
                             ],
                         ],
                     ],
+                    'authenticate' => array(
+                        'type' => 'Segment',
+                        'options' => array(
+                            'route' => '/authenticate/:type',
+                            'defaults' => array(
+                                'controller' => 'UnicaenAuth\Controller\Auth', // remplace 'zfcuser'
+                                'action'     => 'authenticate',
+                            ),
+                        ),
+                    ),
                     'logout'   => [
                         'type'    => 'Literal',
                         'options' => [
                             'route'    => '/deconnexion',
                             'defaults' => [
-                                'controller' => 'zfcuser',
+                                'controller' => 'UnicaenAuth\Controller\Auth', // remplace 'zfcuser'
                                 'action'     => 'logout',
                             ],
                         ],
@@ -462,9 +587,6 @@ return [
             'UnicaenAuth\View\RedirectionStrategy'    => 'UnicaenAuth\View\RedirectionStrategy',
             'UnicaenAuth\Service\CategoriePrivilege'  => 'UnicaenAuth\Service\CategoriePrivilegeService',
         ],
-        'abstract_factories' => [
-            'UnicaenAuth\Authentication\Adapter\AbstractFactory',
-        ],
         'factories'          => [
             'unicaen-auth_module_options'              => 'UnicaenAuth\Options\ModuleOptionsFactory',
             'zfcuser_auth_service'                     => 'UnicaenAuth\Authentication\AuthenticationServiceFactory',
@@ -480,10 +602,15 @@ return [
             'UnicaenAuth\Service\Privilege'            => 'UnicaenAuth\Service\PrivilegeServiceFactory',
             'BjyAuthorize\Service\Authorize'           => 'UnicaenAuth\Service\AuthorizeServiceFactory', // substituion
             'zfcuser_redirect_callback'                => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion
+            CasService::class                          => CasServiceFactory::class,
             ShibService::class                         => ShibServiceFactory::class,
             'UnicaenAuth\Service\UserContext'          => UserContextFactory::class,
             'zfcuser_user_mapper'                      => UserMapperFactory::class,
             'MouchardCompleterAuth'        => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory',
+            'UnicaenAuth\Authentication\Adapter\Ldap' => LdapAdapterFactory::class,
+            'UnicaenAuth\Authentication\Adapter\Db'   => DbAdapterFactory::class,
+            'UnicaenAuth\Authentication\Adapter\Cas'  => CasAdapterFactory::class,
+            'UnicaenAuth\Authentication\Adapter\Shib' => ShibAdapterFactory::class,
             'UnicaenAuth\Authentication\Storage\Db'   => DbFactory::class,
             'UnicaenAuth\Authentication\Storage\Ldap' => LdapFactory::class,
             'UnicaenAuth\Authentication\Storage\Shib' => ShibFactory::class,
@@ -492,6 +619,10 @@ return [
             'UnicaenAuth\Guard\PrivilegeRoute'        => PrivilegeRouteFactory::class,
             'UnicaenAuth\Provider\Rule\PrivilegeRuleProvider' => PrivilegeRuleProviderFactory::class,
 
+            CasLoginForm::class => CasLoginFormFactory::class,
+            ShibLoginForm::class => ShibLoginFormFactory::class,
+            'ZfcUser\Authentication\Adapter\AdapterChain' => AdapterChainServiceFactory::class,
+
             'UnicaenApp\HistoriqueListener' => HistoriqueListenerFactory::class,
             'UnicaenAuth\HistoriqueListener' => HistoriqueListenerFactory::class,
             \UnicaenAuth\Event\EventManager::class => \UnicaenAuth\Event\EventManagerFactory::class
@@ -543,9 +674,12 @@ return [
             'userProfileSelect'          => UserProfileSelect::class,
             'userProfileSelectRadioItem' => UserProfileSelectRadioItem::class,
             'userUsurpation'             => UserUsurpationHelper::class,
+            'dbConnect'                  => DbConnectViewHelper::class,
             'localConnect'               => LocalConnectViewHelper::class,
             'ldapConnect'                => LdapConnectViewHelper::class,
             'shibConnect'                => ShibConnectViewHelper::class,
+            'casConnect'                 => CasConnectViewHelper::class,
+            'connect'                    => ConnectViewHelper::class,
         ],
         'factories' => [
             UserConnection::class             => UserConnectionFactory::class,
@@ -556,12 +690,15 @@ return [
             UserProfileSelect::class          => UserProfileSelectFactory::class,
             UserProfileSelectRadioItem::class => UserProfileSelectRadioItemFactory::class,
             UserUsurpationHelper::class       => UserUsurpationHelperFactory::class,
+            DbConnectViewHelper::class        => DbConnectViewHelperFactory::class,
             LocalConnectViewHelper::class     => LocalConnectViewHelperFactory::class,
             LdapConnectViewHelper::class      => LdapConnectViewHelperFactory::class,
             ShibConnectViewHelper::class      => ShibConnectViewHelperFactory::class,
+            CasConnectViewHelper::class       => CasConnectViewHelperFactory::class,
         ],
         'invokables' => [
             'appConnection' => 'UnicaenAuth\View\Helper\AppConnection',
+            ConnectViewHelper::class,
         ],
     ],
 ];
\ No newline at end of file
diff --git a/config/unicaen-auth.global.php.dist b/config/unicaen-auth.global.php.dist
index 60918a2a7b091517f00140b501413de9c7dd1654..ef59c94a8be99cc052b2282fa3ef19513628812c 100644
--- a/config/unicaen-auth.global.php.dist
+++ b/config/unicaen-auth.global.php.dist
@@ -1,49 +1,8 @@
 <?php
 /**
  * UnicaenAuth Global Configuration
- *
- * If you have a ./config/autoload/ directory set up for your project, you can
- * drop this config file in it and change the values as you wish.
  */
 $settings = [
-
-    /**
-     * Configuration de l'authentification locale.
-     */
-    'local' => [
-        /**
-         * Affichage ou non du formulaire d'authentification avec un compte local.
-         */
-        'enabled' => false,
-    ],
-
-    /**
-     * Configuration de l'authentification LDAP.
-     */
-    'ldap' => [
-        /**
-         * Affichage ou non du formulaire d'authentification via l'annuaire LDAP.
-         * NB: en réalité cela permet aussi l'authentification avec un compte local.
-         */
-        'enabled' => true,
-    ],
-
-    /**
-     * Configuration de l'authentification Shibboleth.
-     */
-    'shibboleth' => [
-        /**
-         * Affichage ou non du formulaire d'authentification via l'annuaire LDAP.
-         * NB: en réalité cela permet aussi l'authentification avec un compte local.
-         */
-        'enable' => false,
-
-        /**
-         * URL de déconnexion.
-         */
-        'logout_url' => '/Shibboleth.sso/Logout?return=', // NB: '?return=' semble obligatoire!
-    ],
-
     /**
      * Flag indiquant si l'utilisateur authenitifié avec succès via l'annuaire LDAP doit
      * être enregistré/mis à jour dans la table des utilisateurs de l'appli.
diff --git a/config/unicaen-auth.local.php.dist b/config/unicaen-auth.local.php.dist
index 39eadae5ffd7a2158d6fe0e3e78b065fa155515a..1a1cabf991baa18c374a666b9812a08e90b85dc4 100644
--- a/config/unicaen-auth.local.php.dist
+++ b/config/unicaen-auth.local.php.dist
@@ -1,17 +1,52 @@
 <?php
 
+use UnicaenAuth\Authentication\Adapter\Shib;
+use UnicaenAuth\Authentication\Adapter\Cas;
+use UnicaenAuth\Authentication\Adapter\Ldap;
+use UnicaenAuth\Authentication\Adapter\Db;
+
 return [
     'unicaen-auth' => [
-
         /**
-         * Configuration de l'authentification Shibboleth.
+         * Configuration de l'authentification via la fédération d'identité (Shibboleth).
          */
-        'shibboleth' => [
-            'enable' => false,
+        'shib' => [
+            /**
+             * Ordre d'affichage du formulaire de connexion.
+             */
+            'order' => 1,
+
+            /**
+             * Activation ou non de ce mode d'authentification.
+             */
+            'enabled' => true,
+
+            /**
+             * Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire de connexion.
+             */
+            'description' =>
+                "Cliquez sur le bouton ci-dessous pour accéder à l'authentification via la fédération d'identité. " .
+                "<strong>NB: Vous devrez utiliser votre compte " .
+                "&laquo; <a href='http://vie-etudiante.unicaen.fr/vie-numerique/etupass/'>etupass</a> &raquo; " .
+                "pour vous authentifier...</strong>",
+
+            /**
+             * URL de déconnexion.
+             */
+            'logout_url' => '/Shibboleth.sso/Logout?return=', // NB: '?return=' semble obligatoire!
+
+            /**
+             * Simulation d'authentification d'un utilisateur.
+             */
             'simulate' => [
                 'eppn'        => 'gauthierb@unicaen.fr',
                 'supannEmpId' => '00021237',
             ],
+
+            /**
+             * Alias éventuels des clés renseignées par Shibboleth dans la variable superglobale $_SERVER
+             * une fois l'authentification réussie.
+             */
             'aliases' => [
                 'eppn'                   => 'HTTP_EPPN',
                 'mail'                   => 'HTTP_MAIL',
@@ -24,6 +59,10 @@ return [
                 'givenName'              => 'HTTP_GIVENNAME',
             ],
             /*
+            /**
+             * Clés dont la présence sera requise par l'application dans la variable superglobale $_SERVER
+             * une fois l'authentification réussie.
+             */
             'required_attributes' => [
                 'eppn',
                 'mail',
@@ -38,16 +77,49 @@ return [
         ],
 
         /**
-         * Paramètres de connexion au serveur CAS :
-         * - pour désactiver l'authentification CAS, le tableau 'cas' doit être vide.
-         * - pour l'activer, renseigner les paramètres.
+         * Configuration de l'authentification LDAP (compte établissement).
+         */
+        'ldap' => [
+            'order' => 2,
+            'enabled' => true,
+
+            /**
+             * Type de substitution.
+             * Permet de "fusionner" les types d'authentification locale (db) et établissement (ldap) et donc leurs
+             * formulaires de connexion respectifs.
+             */
+            'type' => 'local',
+        ],
+
+        /**
+         * Configuration de l'authentification locale (compte propre à l'appli).
+         */
+        'db' => [
+            'order' => 3,
+            'enabled' => true,
+            'type' => 'local',
+
+            /**
+             * Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire d'authentification.
+             * (NB: Si l'authentification LDAP est également activée, c'est cette description qui sera utilisée)
+             */
+            'description' => "Utilisez ce formulaire si vous possédez un compte local propre à l'application.",
+        ],
+
+        /**
+         * Configuration de l'authentification centralisée (CAS).
          */
-         /*
         'cas' => [
+            'order' => 4,
+            'enabled' => false,
+
+            /**
+             * Infos de connexion au serveur CAS.
+             */
             'connection' => [
                 'default' => [
                     'params' => [
-                        'hostname' => 'cas.unicaen.fr',
+                        'hostname' => 'host.domain.fr',
                         'port'     => 443,
                         'version'  => "2.0",
                         'uri'      => "",
@@ -56,7 +128,6 @@ return [
                 ],
             ],
         ],
-        */
 
         /**
          * Identifiants de connexion LDAP autorisés à faire de l'usurpation d'identité.
diff --git a/doc/authentification.md b/doc/authentification.md
index 908b97938dd750ff179777aa7e125ff410144332..d497982486c9c5330928b026cccb08685c14aa39 100644
--- a/doc/authentification.md
+++ b/doc/authentification.md
@@ -1,36 +1,53 @@
 # Authentification
 
-## Sources d'authentification
+## Types d'authentification
 
-Les 3 sources suivantes sont sollicitées successivement jusqu'à ce que l'une d'entre elles valide l'authentification de l'utilisateur.
+Quatre types d'authentification sont activables dans la configuration du module. 
 
-1/ Annuaire LDAP
+1/ Authentification via la fédération d'identité Renater (Shibboleth)
 
-- La connexion à l'annuaire LDAP est requise pour authentifier (ldap_bind) et récupérer les infos concernant l'utilisateur (cf. configuration du module UnicaenApp).
-- Il est possible d'enregistrer systématiquement l'utilisateur authentifié dans la base de données de l'application.
+- Ce type d'authentification requiert l'installation d'un module Shibboleth sur le serveur d'application, configuré
+  pour se déclencher sur l'URL `/auth/shibboleth`, exemple : `https://sygal.univ.fr/auth/shibboleth`.
+- Clé de configuration `shib`.
 
-2/ Table des utilisateurs
+2/ Avec un compte local établissement (LDAP)
+
+- La connexion à un annuaire LDAP est requise pour authentifier et récupérer les infos concernant l'utilisateur 
+  (cf. configuration du module unicaen/app ou unicaen/ldap).
+- Clé de configuration `ldap`.
+
+3/ Avec un compte local propre à l'appli (DB) 
 
 - Il peut arriver qu'une appli ait besoin d'authentifier des personnes n'existant pas dans l'annuaire LDAP.
 - Pour donner accès à l'application à un nouvel utilisateur, 2 solutions :
-  - Un informaticien crée à la main l'utilisateur dans la table des utilisateurs ; le mot de passe doit être chiffré avec “Bcrypt”
-  (exemple en ligne de commande à la racine de votre projet : `php --run 'require "vendor/autoload.php"; $bcrypt = new Zend\Crypt\Password\Bcrypt(); var_dump($bcrypt->create("azerty"));'`).
-  - Si la fonctionnalité est activée (fournie par le module "zf-commons/zfc-user" dont dépend le module UnicaenAuth), l'utilisateur s'enregistre lui-même dans la table des utilisateurs via un formulaire de l'application (le lien figure sous le formulaire de connexion à l'appli).
+  - Un informaticien crée à la main l'utilisateur dans la table des utilisateurs ; le mot de passe doit être chiffré 
+    avec “Bcrypt” (exemple en ligne de commande à la racine de votre projet : 
+    `php --run 'require "vendor/autoload.php"; $bcrypt = new Zend\Crypt\Password\Bcrypt(); var_dump($bcrypt->create("azerty"));'`).
+  - Si la fonctionnalité est activée (fournie par le module "zf-commons/zfc-user" dont dépend le module unicaen/auth), 
+    l'utilisateur s'enregistre lui-même dans la table des utilisateurs via un formulaire de l'application (le lien figure 
+    sous le formulaire de connexion à l'appli).
+- Clé de configuration `db`.
 
-3/ Serveur CAS
+4/ Via un serveur d'authentification centralisée (CAS)
 
 - L'authentification est déléguée au serveur CAS grâce au module jasig/phpcas (bibliothèque phpCAS).
-- NB: La connexion à l'annuaire LDAP est tout de même requise pour récupérer les infos concernant l'utilisateur (cf. configuration du module UnicaenApp).
+- NB: La connexion à l'annuaire LDAP est tout de même requise pour récupérer les infos concernant l'utilisateur 
+  (cf. configuration du module unicaen/app).
+- Clé de config `cas`.
 
 ## Événement UserAuthenticatedEvent
 
-Si vous avez activé l'enregistrement automatique de l'utilisateur authentifié dans la base de données de votre application, la classe abstraite UnicaenAuth\Event\Listener\AuthenticatedUserSavedAbstractListener peut vous intéresser.
+Si vous avez activé l'enregistrement automatique de l'utilisateur authentifié dans la base de données de votre 
+application, la classe abstraite UnicaenAuth\Event\Listener\AuthenticatedUserSavedAbstractListener peut vous intéresser.
 
-Elle vous procure un moyen de “faire quelque chose” juste avant que l'entité utilisateur (fraîchement authentifié via LDAP) ne soit persistée. L'idée est d'écouter un événement particulier déclenché lors du processus d'authentification de l'utilisateur.
+Elle vous procure un moyen de “faire quelque chose” juste avant que l'entité utilisateur (fraîchement authentifié 
+via LDAP) ne soit persistée. L'idée est d'écouter un événement particulier déclenché lors du processus d'authentification de l'utilisateur.
 
-*Attention! Cet événement est déclenché par l'authentification LDAP, mais pas par l'authentification à partir d'une table locale en base de données.*
+*Attention! Cet événement est déclenché par l'authentification LDAP, mais pas par l'authentification à partir d'une 
+table locale en base de données.*
 
-*Si vous avez mis en place (en plus ou à la place de l'authentification LDAP) une authentification à partir d'une table locale, écoutez plutôt l'événement authentication.success déclenché par le module ZfcUser une fois que l'authentification a réussi. Exemple :*
+*Si vous avez mis en place (en plus ou à la place de l'authentification LDAP) une authentification à partir d'une 
+table locale, écoutez plutôt l'événement authentication.success déclenché par le module ZfcUser une fois que l'authentification a réussi. Exemple :*
 
 Module.php
 
diff --git a/doc/configuration.md b/doc/configuration.md
index 82600bee7a93359853932430dc5380ad169fb43e..451c1363689bb17c8a911f2f21068ee58717c17a 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -1,8 +1,11 @@
-# Configuration
+Configuration
+=============
 
 Il s'agit ici d'adapter certaines options de configuration des modules.
 
-## Configuration globale
+
+Configuration globale
+---------------------
 
 - Copier/coller/renommer le fichier config/unicaen-auth.global.php.dist du module vers config/autoload/unicaen-auth.global.php de votre projet.
 - Adapter les informations suivantes à votre contexte (sans modifier le nom des clés)…
@@ -43,37 +46,27 @@ Le système de gestion des privilèges d'UnicaenAuth est associé à une interfa
 
 Un menu “Droits d'accès” est affiché par défaut dans votre barre de menu principale. Ceci peut bien entendu être modifié selon vos souhaits dans le fichier de configuration global d'UnicaenAuth placé dans votre projet.
 
-## Configuration locale
+
+Configuration locale
+---------------------
 
 - Copier/coller/renommer le fichier config/unicaen-auth.local.php.dist du module vers config/autoload/unicaen-auth.local.php de votre projet.
 - Adapter les informations suivantes à votre contexte (sans modifier le nom des clés)…
 
-### Authentification centralisée
-
-Clé 'cas' : décommenter pour activer l'authentification CAS, commenter pour la désactiver, exemple :
-
-unicaen-auth.local.php
-
-        'cas' => array(
-            'connection' => array(
-                'default' => array(
-                    'params' => array(
-                        'hostname' => 'cas.unicaen.fr',
-                        'port' => 443,
-                        'version' => "2.0",
-                        'uri' => "",
-                        'debug' => false,
-                    ),
-                ),
-            ),
-        ),
+### Authentification
 
-### Usurpation d'identité
+Cf. [Authentification](./authentification.md).
 
-Clé 'usurpation_allowed_usernames' : liste des identifiants de connexion des utilisateurs (issus de l'annuaire LDAP) autorisés à se connecter à l'appli sous l'identité de n'importe quel utilisateur (issus de l'annuaire LDAP ou de la base de données d'authentification), exemple :
+## Usurpation d'identité
 
-unicaen-auth.local.php
+Clé `usurpation_allowed_usernames` : liste des identifiants de connexion des utilisateurs
+autorisés à se connecter à l'appli sous l'identité de n'importe quel utilisateur (issus de l'annuaire LDAP ou de la 
+base de données d'authentification), exemple :
 
-        'usurpation_allowed_usernames' => array('gauthierb'),
+`unicaen-auth.local.php`
 
-D'après cet exemple, l'utilisateur “gauthierb” est habilité à saisir dans le formulaire de connexion à l'appli l'identifiant “gauthierb=fernagut” et son mot de passe habituel pour se faire passer pour l'utilisateur “fernagut”.
+        'usurpation_allowed_usernames' => ['login'],
+        
+D'après cet exemple, l'utilisateur dont l'identifiant de connexion est `login` est habilité à saisir dans le formulaire 
+de connexion l'identifiant `login=victime` et son mot de passe habituel pour se faire passer pour l'utilisateur dont
+l'identifiant est `victime`.
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/AbstractAdapter.php b/src/UnicaenAuth/Authentication/Adapter/AbstractAdapter.php
new file mode 100644
index 0000000000000000000000000000000000000000..9dc0eac94d6e6018ab38a63449ad7e6c144d9cbb
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/AbstractAdapter.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use Zend\Authentication\Storage;
+use ZfcUser\Authentication\Adapter\ChainableAdapter;
+
+abstract class AbstractAdapter implements ChainableAdapter
+{
+    /**
+     * @var string
+     */
+    protected $type;
+
+    /**
+     * @var Storage\StorageInterface
+     */
+    protected $storage;
+
+    /**
+     * @param string $type
+     * @return self
+     */
+    public function setType(string $type): self
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * Returns the persistent storage handler
+     *
+     * Session storage is used by default unless a different storage adapter has been set.
+     *
+     * @return Storage\StorageInterface
+     */
+    public function getStorage()
+    {
+        if (null === $this->storage) {
+            $this->setStorage(new Storage\Session(get_class($this)));
+        }
+
+        return $this->storage;
+    }
+
+    /**
+     * Sets the persistent storage handler
+     *
+     * @param  Storage\StorageInterface $storage
+     * @return AbstractAdapter Provides a fluent interface
+     */
+    public function setStorage(Storage\StorageInterface $storage)
+    {
+        $this->storage = $storage;
+        return $this;
+    }
+
+    /**
+     * Check if this adapter is satisfied or not
+     *
+     * @return bool
+     */
+    public function isSatisfied()
+    {
+        $storage = $this->getStorage()->read();
+        return (isset($storage['is_satisfied']) && true === $storage['is_satisfied']);
+    }
+
+    /**
+     * Set if this adapter is satisfied or not
+     *
+     * @param bool $bool
+     * @return AbstractAdapter
+     */
+    public function setSatisfied($bool = true)
+    {
+        $storage = $this->getStorage()->read() ?: array();
+        $storage['is_satisfied'] = $bool;
+        $this->getStorage()->write($storage);
+        return $this;
+    }
+}
diff --git a/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php b/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
deleted file mode 100644
index fa4e56d78416660267526cc5d4a33263b9a264f0..0000000000000000000000000000000000000000
--- a/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-
-namespace UnicaenAuth\Authentication\Adapter;
-
-use Interop\Container\ContainerInterface;
-use UnicaenApp\Exception\LogicException;
-use UnicaenAuth\Options\ModuleOptions;
-use UnicaenAuth\Service\User;
-use UnicaenAuth\Event\EventManager;
-use Zend\EventManager\EventManagerAwareInterface;
-use Zend\Router\Http\TreeRouteStack;
-use Zend\ServiceManager\AbstractFactoryInterface;
-use Zend\ServiceManager\ServiceLocatorInterface;
-use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
-
-/**
- * Description of AbstractFactory
- *
- * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
- */
-class AbstractFactory implements AbstractFactoryInterface
-{
-    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
-    {
-        return $this->canCreate($serviceLocator, $requestedName);
-    }
-
-    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
-    {
-        return $this->__invoke($serviceLocator, $requestedName);
-    }
-
-    public function canCreate(ContainerInterface $container, $requestedName)
-    {
-        return strpos($requestedName, __NAMESPACE__) === 0 && class_exists($requestedName);
-    }
-
-    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
-    {
-        switch ($requestedName) {
-            case __NAMESPACE__ . '\Ldap':
-                $adapter = new Ldap();
-                break;
-            case __NAMESPACE__ . '\Db':
-                $adapter = new Db();
-                break;
-            case __NAMESPACE__ . '\Cas':
-                $adapter = new Cas();
-                break;
-            //
-            // NB: pour faire simple, la stratégie de créer un adapter pour l'auth Shibboleth n'a pas été retenue.
-            //
-            // case __NAMESPACE__ . '\Shib':
-            //     $adapter = new Shib();
-            //     break;
-            default:
-                throw new LogicException("Service demandé inattendu : '$requestedName'!");
-                break;
-        }
-
-        $this->injectDependencies($adapter, $container);
-
-        if ($adapter instanceof EventManagerAwareInterface) {
-            /** @var EventManager $eventManager */
-            $eventManager = $container->get(EventManager::class);
-            $adapter->setEventManager($eventManager);
-            $userService = $container->get('unicaen-auth_user_service'); /* @var $userService \UnicaenAuth\Service\User */
-            $eventManager->attach('userAuthenticated', [$userService, 'userAuthenticated'], 100);
-            $eventManager->attach('clear', function() use ($adapter){
-                $adapter->getStorage()->clear();
-            });
-        }
-
-        return $adapter;
-    }
-
-    /**
-     * @param Ldap|Db|Cas        $adapter
-     * @param ContainerInterface $container
-     */
-    private function injectDependencies($adapter, ContainerInterface $container)
-    {
-        switch (true) {
-
-            case $adapter instanceof Ldap:
-                /** @var User $userService */
-                $userService = $container->get('unicaen-auth_user_service');
-                $adapter->setUserService($userService);
-
-                /** @var LdapPeopleMapper $ldapPeopleMapper */
-                $ldapPeopleMapper = $container->get('ldap_people_mapper');
-                $adapter->setLdapPeopleMapper($ldapPeopleMapper);
-
-                $options = array_merge(
-                    $container->get('zfcuser_module_options')->toArray(),
-                    $container->get('unicaen-auth_module_options')->toArray());
-                $adapter->setOptions(new ModuleOptions($options));
-
-                /** @var \UnicaenApp\Options\ModuleOptions $appModuleOptions */
-                $appModuleOptions = $container->get('unicaen-app_module_options');
-                $adapter->setAppModuleOptions($appModuleOptions);
-
-                break;
-
-            case $adapter instanceof Cas:
-                /** @var User $userService */
-                $userService = $container->get('unicaen-auth_user_service');
-                $adapter->setUserService($userService);
-
-                /** @var mixed $router */
-                $router = $container->get('router');
-                $adapter->setRouter($router);
-
-                $options = array_merge(
-                    $container->get('zfcuser_module_options')->toArray(),
-                    $container->get('unicaen-auth_module_options')->toArray());
-                $adapter->setOptions(new ModuleOptions($options));
-
-                /** @var LdapPeopleMapper $ldapPeopleMapper */
-                $ldapPeopleMapper = $container->get('ldap_people_mapper');
-                $adapter->setLdapPeopleMapper($ldapPeopleMapper);
-
-                break;
-
-            default:
-                break;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/AdapterChain.php b/src/UnicaenAuth/Authentication/Adapter/AdapterChain.php
new file mode 100644
index 0000000000000000000000000000000000000000..b72a4aa062fac9280a770787d11707d99035963c
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/AdapterChain.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use Zend\Stdlib\RequestInterface as Request;
+use Zend\Stdlib\ResponseInterface as Response;
+use ZfcUser\Exception;
+
+class AdapterChain extends \ZfcUser\Authentication\Adapter\AdapterChain
+{
+    /**
+     * prepareForAuthentication
+     *
+     * @param  Request $request
+     * @return Response|bool
+     * @throws Exception\AuthenticationEventException
+     */
+    public function prepareForAuthentication(Request $request)
+    {
+        $e = $this->getEvent();
+        $e->setRequest($request);
+
+        $this->getEventManager()->trigger('authenticate.pre', $e);
+
+        $result = $this->getEventManager()->triggerUntil(function ($test) {
+            return ($test instanceof Response);
+        }, 'authenticate', $e);
+
+        if ($result->stopped()) {
+            if ($result->last() instanceof Response) {
+                return $result->last();
+            }
+
+            throw new Exception\AuthenticationEventException(
+                sprintf(
+                    'Auth event was stopped without a response. Got "%s" instead',
+                    is_object($result->last()) ? get_class($result->last()) : gettype($result->last())
+                )
+            );
+        }
+
+        if ($e->getIdentity()) {
+            $this->getEventManager()->trigger('authenticate.success', $e);
+            return true;
+        }
+
+        $this->getEventManager()->trigger('authenticate.fail', $e);
+
+        return false;
+    }
+
+    /**
+     * logoutAdapters
+     *
+     * @return Response|null
+     */
+    public function logoutAdapters()
+    {
+        //Adapters might need to perform additional cleanup after logout
+        $responseCollection = $this->getEventManager()->triggerUntil(function ($test) {
+            return ($test instanceof Response);
+        }, 'logout', $this->getEvent());
+
+        if ($responseCollection->stopped()) {
+            if ($responseCollection->last() instanceof Response) {
+                return $responseCollection->last();
+            }
+
+            throw new Exception\AuthenticationEventException(
+                sprintf(
+                    'Auth event was stopped without a response. Got "%s" instead',
+                    is_object($responseCollection->last()) ? get_class($responseCollection->last()) : gettype($responseCollection->last())
+                )
+            );
+        }
+
+        return null;
+    }
+}
diff --git a/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php b/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..0df001c2128e9aa31e8a0f60e92a1b70b30420b1
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php
@@ -0,0 +1,70 @@
+<?php
+namespace UnicaenAuth\Authentication\Adapter;
+
+use Interop\Container\ContainerInterface;
+use ZfcUser\Authentication\Adapter\Exception\OptionsNotFoundException;
+use ZfcUser\Options\ModuleOptions;
+
+class AdapterChainServiceFactory
+{
+    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
+    {
+        $chain = new AdapterChain();
+
+        $options = $this->getOptions($container);
+
+        //iterate and attach multiple adapters and events if offered
+        foreach ($options->getAuthAdapters() as $priority => $adapterName) {
+            $adapter = $container->get($adapterName);
+
+            if (is_callable(array($adapter, 'authenticate'))) {
+                $chain->getEventManager()->attach('authenticate', array($adapter, 'authenticate'), $priority);
+            }
+
+            if (is_callable(array($adapter, 'logout'))) {
+                $chain->getEventManager()->attach('logout', array($adapter, 'logout'), $priority);
+            }
+        }
+
+        return $chain;
+    }
+
+    /**
+     * @var ModuleOptions
+     */
+    protected $options;
+
+    /**
+     * set options
+     *
+     * @param ModuleOptions $options
+     * @return AdapterChainServiceFactory
+     */
+    public function setOptions(ModuleOptions $options)
+    {
+        $this->options = $options;
+        return $this;
+    }
+
+    /**
+     * get options
+     *
+     * @param ContainerInterface|null $container (optional) Service Locator
+     * @return ModuleOptions $options
+     */
+    public function getOptions(ContainerInterface $container = null)
+    {
+        if (!$this->options) {
+            if (!$container) {
+                throw new OptionsNotFoundException(
+                    'Options were tried to retrieve but not set ' .
+                    'and no service locator was provided'
+                );
+            }
+
+            $this->setOptions($container->get('zfcuser_module_options'));
+        }
+
+        return $this->options;
+    }
+}
diff --git a/src/UnicaenAuth/Authentication/Adapter/Cas.php b/src/UnicaenAuth/Authentication/Adapter/Cas.php
index 12cae0acbebdafc23a6f1618de849084b5d40312..7d76936fd0d30decff5f4dd8f8db63338b2eac73 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Cas.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Cas.php
@@ -5,37 +5,31 @@ namespace UnicaenAuth\Authentication\Adapter;
 use Exception;
 use phpCAS;
 use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
-use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
 use UnicaenAuth\Service\User;
 use Zend\Authentication\Exception\UnexpectedValueException;
 use Zend\Authentication\Result as AuthenticationResult;
 use Zend\EventManager\Event;
 use Zend\EventManager\EventInterface;
-use Zend\EventManager\EventManager;
-use Zend\EventManager\EventManagerAwareInterface;
-use Zend\EventManager\EventManagerInterface;
 use Zend\Router\RouteInterface;
 use Zend\Router\RouteStackInterface;
-use ZfcUser\Authentication\Adapter\AbstractAdapter;
 use ZfcUser\Authentication\Adapter\ChainableAdapter;
-use Zend\Authentication\Storage\Session;
 
 /**
  * CAS authentication adpater
  *
  * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
  */
-class Cas extends AbstractAdapter implements EventManagerAwareInterface
+class Cas extends AbstractAdapter
 {
-    /**
-     * @var EventManager
-     */
-    protected $eventManager;
+    use ModuleOptionsAwareTrait;
+
+    const TYPE = 'cas';
 
     /**
-     * @var ModuleOptions
+     * @var string
      */
-    protected $options;
+    protected $type = self::TYPE;
 
     /**
      * @var array
@@ -92,11 +86,16 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
         // Si un jour c'est un AdapterChainEvent qui est attendu, plus besoin de faire $e->getTarget().
         $e = $e->getTarget();
 
+        $type = $e->getRequest()->getPost()->get('type');
+        if ($type !== $this->type) {
+            return;
+        }
+
 //        if ($e->getIdentity()) {
 //            return;
 //        }
-	/* DS : modification liée à une boucle infinie lors de l'authentification CAS */
-	if ($this->isSatisfied()) {
+        /* DS : modification liée à une boucle infinie lors de l'authentification CAS */
+        if ($this->isSatisfied()) {
             $storage = $this->getStorage()->read();
             $e->setIdentity($storage['identity'])
                     ->setCode(AuthenticationResult::SUCCESS)
@@ -104,9 +103,8 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
             return;
         }
 
-        $config = $this->getOptions()->getCas();
-        if (!$config) {
-            return; // NB: l'authentification CAS est désactivée ssi le tableau des options est vide
+        if (! $this->isEnabled()) {
+            return;
         }
 
         error_reporting($oldErrorReporting = error_reporting() & ~E_NOTICE);
@@ -135,6 +133,23 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
         $this->userService->userAuthenticated($ldapPeople);
     }
 
+    /**
+     * @return bool
+     */
+    protected function isEnabled()
+    {
+        $config = $this->moduleOptions->getCas();
+
+        if (! $config) {
+            return false;
+        }
+        if (isset($config['enabled'])) {
+            return (bool) $config['enabled'];
+        }
+
+        return true;
+    }
+
     /**
      *
      * @param Event $e
@@ -142,8 +157,13 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
      */
     public function logout(Event $e)
     {
-        if (!$this->getOptions()->getCas()) {
-            return; // NB: l'authentification CAS est désactivée ssi le tableau des options est vide
+        if (! $this->isEnabled()) {
+            return;
+        }
+
+        $storage = $this->getStorage()->read();
+        if (! isset($storage['identity'])) {
+            return;
         }
 
         $returnUrl = $this->router->getRequestUri()->setPath($this->router->getBaseUrl())->toString();
@@ -168,7 +188,7 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
         }
 
         if (null === $this->casOptions) {
-            $config = $this->getOptions()->getCas();
+            $config = $this->moduleOptions->getCas();
             if (!isset($config['connection']['default']['params']) || !$config['connection']['default']['params']) {
                 throw new Exception("Les paramètres de connexion au serveur CAS sont invalides.");
             }
@@ -201,28 +221,6 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
         return $this;
     }
 
-    /**
-     * @param ModuleOptions $options
-     */
-    public function setOptions(ModuleOptions $options)
-    {
-        $this->options = $options;
-    }
-
-    /**
-     * @return ModuleOptions
-     */
-    public function getOptions()
-    {
-//        if (!$this->options instanceof ModuleOptions) {
-//            $options = array_merge(
-//                    $this->serviceLocator->get('zfcuser_module_options')->toArray(),
-//                    $this->serviceLocator->get('unicaen-auth_module_options')->toArray());
-//            $this->setOptions(new ModuleOptions($options));
-//        }
-        return $this->options;
-    }
-
     /**
      * get ldap people mapper
      *
@@ -246,30 +244,14 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
         return $this;
     }
 
-    /**
-     * Retrieve EventManager instance
-     *
-     * @return EventManagerInterface
-     */
-    public function getEventManager()
-    {
-        return $this->eventManager;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function setEventManager(EventManagerInterface $eventManager)
-    {
-        $this->eventManager = $eventManager;
-        return $this;
-    }
-
     /**
      * @param RouteInterface $router
      */
     public function reconfigureRoutesForCasAuth(RouteInterface $router)
     {
+        if (! $this->isEnabled()) {
+            return;
+        }
         if(!$router instanceof RouteStackInterface) {
             return;
         }
@@ -289,9 +271,9 @@ class Cas extends AbstractAdapter implements EventManagerAwareInterface
                 'may_terminate' => true,
                 'child_routes'  => [
                     'login'  => [
-                        'type'    => 'Literal',
+                        'type'    => 'Segment',
                         'options' => [
-                            'route'    => '/connexion',
+                            'route'    => '/connexion[/:type]',
                             'defaults' => [
                                 'controller' => 'zfcuser',
                                 'action'     => 'authenticate', // zappe l'action 'login'
diff --git a/src/UnicaenAuth/Authentication/Adapter/CasAdapterFactory.php b/src/UnicaenAuth/Authentication/Adapter/CasAdapterFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..a08e24c7a60b18c29c6a5c485e751d72ceb20261
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/CasAdapterFactory.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use Interop\Container\ContainerInterface;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\User;
+use Zend\Authentication\Storage\Session;
+
+class CasAdapterFactory
+{
+    /**
+     * @param ContainerInterface $container
+     * @param string $requestedName
+     * @param array|null $options
+     * @return Cas
+     */
+    public function __invoke(ContainerInterface $container, string $requestedName, array $options = null)
+    {
+        $adapter = new Cas();
+        $adapter->setStorage(new Session(Cas::class));
+
+        $this->injectDependencies($adapter, $container);
+
+//        /** @var EventManager $eventManager */
+//        $eventManager = $container->get(EventManager::class);
+//        $adapter->setEventManager($eventManager);
+//        $userService = $container->get('unicaen-auth_user_service'); /* @var $userService \UnicaenAuth\Service\User */
+//        $eventManager->attach('userAuthenticated', [$userService, 'userAuthenticated'], 100);
+//        $eventManager->attach('clear', function() use ($adapter){
+//            $adapter->getStorage()->clear();
+//        });
+
+        return $adapter;
+    }
+
+    /**
+     * @param Cas $adapter
+     * @param ContainerInterface $container
+     */
+    private function injectDependencies(Cas $adapter, ContainerInterface $container)
+    {
+        /** @var User $userService */
+        $userService = $container->get('unicaen-auth_user_service');
+        $adapter->setUserService($userService);
+
+        /** @var mixed $router */
+        $router = $container->get('router');
+        $adapter->setRouter($router);
+
+        $options = array_merge(
+            $container->get('zfcuser_module_options')->toArray(),
+            $container->get('unicaen-auth_module_options')->toArray());
+        $moduleOptions = new ModuleOptions($options);
+        $adapter->setModuleOptions($moduleOptions);
+
+        $substitut = $moduleOptions->getCas()['type'] ?? null;
+        if ($substitut !== null) {
+            $adapter->setType($substitut);
+        }
+
+        /** @var LdapPeopleMapper $ldapPeopleMapper */
+        $ldapPeopleMapper = $container->get('ldap_people_mapper');
+        $adapter->setLdapPeopleMapper($ldapPeopleMapper);
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/Db.php b/src/UnicaenAuth/Authentication/Adapter/Db.php
index 39b1c8d5595d5ff012971ad0586f25e7c42edcb0..f1e2bf5ec6bc112fc1faa64cfa4c7b57acab6499 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Db.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Db.php
@@ -6,9 +6,13 @@ use Interop\Container\ContainerInterface;
 use UnicaenApp\ServiceManager\ServiceLocatorAwareInterface;
 use UnicaenApp\ServiceManager\ServiceLocatorAwareTrait;
 use UnicaenAuth\Options\ModuleOptions;
-use Zend\Authentication\Storage\Session;
+use Zend\Authentication\Result as AuthenticationResult;
+use Zend\Crypt\Password\Bcrypt;
 use Zend\EventManager\EventInterface;
-use Zend\ServiceManager\Exception\ServiceNotFoundException;
+use Zend\ServiceManager\ServiceManager;
+use Zend\Session\Container as SessionContainer;
+use ZfcUser\Entity\UserInterface;
+use ZfcUser\Mapper\UserInterface as UserMapperInterface;
 
 /**
  * Adpater d'authentification à partir de la base de données.
@@ -18,8 +22,35 @@ use Zend\ServiceManager\Exception\ServiceNotFoundException;
  *
  * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
  */
-class Db extends \ZfcUser\Authentication\Adapter\Db implements ServiceLocatorAwareInterface
+class Db extends AbstractAdapter implements ServiceLocatorAwareInterface
 {
+    const TYPE = 'db';
+
+    /**
+     * @var string
+     */
+    protected $type = self::TYPE;
+
+    /**
+     * @var UserMapperInterface
+     */
+    protected $mapper;
+
+    /**
+     * @var callable
+     */
+    protected $credentialPreprocessor;
+
+    /**
+     * @var ServiceManager
+     */
+    protected $serviceManager;
+
+    /**
+     * @var \ZfcUser\Options\ModuleOptions
+     */
+    protected $options;
+
     use ServiceLocatorAwareTrait;
 
     /**
@@ -37,6 +68,15 @@ class Db extends \ZfcUser\Authentication\Adapter\Db implements ServiceLocatorAwa
         return $this;
     }
 
+    /**
+     * Called when user id logged out
+     * @param EventInterface $e
+     */
+    public function logout(EventInterface $e)
+    {
+        $this->getStorage()->clear();
+    }
+
     /**
      * Authentification.
      *
@@ -45,21 +85,178 @@ class Db extends \ZfcUser\Authentication\Adapter\Db implements ServiceLocatorAwa
      */
     public function authenticate(EventInterface $e)
     {
+        $type = $e->getTarget()->getRequest()->getPost()->get('type');
+        if ($type !== $this->type) {
+            return;
+        }
+
         // 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().
         if ($e->getTarget()->getIdentity()) {
             return true;
         }
-        
-        try {
-            $result = parent::authenticate($e);
+
+        $e = $e->getTarget();
+        if ($this->isSatisfied()) {
+            $storage = $this->getStorage()->read();
+            $e->setIdentity($storage['identity'])
+                ->setCode(AuthenticationResult::SUCCESS)
+                ->setMessages(array('Authentication successful.'));
+            return;
+        }
+
+        $identity   = $e->getRequest()->getPost()->get('identity');
+        $credential = $e->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->getOptions()->getAuthIdentityFields();
+        while (!is_object($userObject) && count($fields) > 0) {
+            $mode = array_shift($fields);
+            switch ($mode) {
+                case 'username':
+                    $userObject = $this->getMapper()->findByUsername($identity);
+                    break;
+                case 'email':
+                    $userObject = $this->getMapper()->findByEmail($identity);
+                    break;
+            }
         }
-        catch (ServiceNotFoundException $snfe) {
+
+        if (!$userObject) {
+            $e->setCode(AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND)
+                ->setMessages(array('A record with the supplied identity could not be found.'));
+            $this->setSatisfied(false);
             return false;
         }
 
-        return $result;
+        if ($this->getOptions()->getEnableUserState()) {
+            // Don't allow user to login if state is not in allowed list
+            if (!in_array($userObject->getState(), $this->getOptions()->getAllowedLoginStates())) {
+                $e->setCode(AuthenticationResult::FAILURE_UNCATEGORIZED)
+                    ->setMessages(array('A record with the supplied identity is not active.'));
+                $this->setSatisfied(false);
+                return false;
+            }
+        }
+
+        $bcrypt = new Bcrypt();
+        $bcrypt->setCost($this->getOptions()->getPasswordCost());
+        if (!$bcrypt->verify($credential, $userObject->getPassword())) {
+            // Password does not match
+            $e->setCode(AuthenticationResult::FAILURE_CREDENTIAL_INVALID)
+                ->setMessages(array('Supplied credential is invalid.'));
+            $this->setSatisfied(false);
+            return false;
+        }
+
+        // regen the id
+        $session = new SessionContainer($this->getStorage()->getNameSpace());
+        $session->getManager()->regenerateId();
+
+        // Success!
+        $e->setIdentity($userObject->getId());
+        // 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'] = $e->getIdentity();
+        $this->getStorage()->write($storage);
+        $e->setCode(AuthenticationResult::SUCCESS)
+            ->setMessages(array('Authentication successful.'));
+    }
+
+    protected function updateUserPasswordHash(UserInterface $userObject, $password, Bcrypt $bcrypt)
+    {
+        $hash = explode('$', $userObject->getPassword());
+        if ($hash[2] === $bcrypt->getCost()) {
+            return;
+        }
+        $userObject->setPassword($bcrypt->create($password));
+        $this->getMapper()->update($userObject);
+        return $this;
+    }
+
+    public function preProcessCredential($credential)
+    {
+        $processor = $this->getCredentialPreprocessor();
+        if (is_callable($processor)) {
+            return $processor($credential);
+        }
+
+        return $credential;
+    }
+
+    /**
+     * getMapper
+     *
+     * @return UserMapperInterface
+     */
+    public function getMapper()
+    {
+        if (null === $this->mapper) {
+            $this->mapper = $this->getServiceManager()->get('zfcuser_user_mapper');
+        }
+
+        return $this->mapper;
+    }
+
+    /**
+     * setMapper
+     *
+     * @param UserMapperInterface $mapper
+     * @return \ZfcUser\Authentication\Adapter\Db
+     */
+    public function setMapper(UserMapperInterface $mapper)
+    {
+        $this->mapper = $mapper;
+
+        return $this;
+    }
+
+    /**
+     * Get credentialPreprocessor.
+     *
+     * @return callable
+     */
+    public function getCredentialPreprocessor()
+    {
+        return $this->credentialPreprocessor;
+    }
+
+    /**
+     * Set credentialPreprocessor.
+     *
+     * @param callable $credentialPreprocessor
+     * @return $this
+     */
+    public function setCredentialPreprocessor($credentialPreprocessor)
+    {
+        $this->credentialPreprocessor = $credentialPreprocessor;
+        return $this;
+    }
+
+    /**
+     * Retrieve service manager instance
+     *
+     * @return ServiceManager
+     */
+    public function getServiceManager()
+    {
+        return $this->serviceManager;
+    }
+
+    /**
+     * Set service manager instance
+     *
+     * @param ContainerInterface $serviceManager
+     */
+    public function setServiceManager(ContainerInterface $serviceManager)
+    {
+        $this->serviceManager = $serviceManager;
     }
 
     /**
diff --git a/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php b/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..63abde5271ac8535789094086c0c4022e740c656
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use Interop\Container\ContainerInterface;
+use UnicaenAuth\Options\ModuleOptions;
+use Zend\Authentication\Storage\Session;
+use Zend\EventManager\EventManagerAwareInterface;
+
+class DbAdapterFactory
+{
+    /**
+     * @param ContainerInterface $container
+     * @param string $requestedName
+     * @param array|null $options
+     * @return mixed|Cas|Db|Ldap|EventManagerAwareInterface
+     */
+    public function __invoke(ContainerInterface $container, string $requestedName, array $options = null)
+    {
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
+
+        $adapter = new Db();
+        $adapter->setStorage(new Session(Db::class));
+
+        $substitut = $moduleOptions->getDb()['type'] ?? null;
+        if ($substitut !== null) {
+            $adapter->setType($substitut);
+        }
+
+        return $adapter;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/Ldap.php b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
index 711935d0d4574caa1b1351eefebbc8de25eae25e..ae7e624fd334459723791798a4e80053057a7f5f 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Ldap.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
@@ -5,6 +5,7 @@ namespace UnicaenAuth\Authentication\Adapter;
 use UnicaenApp\Exception\RuntimeException;
 use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
 use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
 use UnicaenAuth\Service\User;
 use Zend\Authentication\Adapter\Ldap as LdapAuthAdapter;
 use Zend\Authentication\Exception\ExceptionInterface;
@@ -14,9 +15,7 @@ use Zend\EventManager\EventInterface;
 use Zend\EventManager\EventManager;
 use Zend\EventManager\EventManagerAwareInterface;
 use Zend\EventManager\EventManagerInterface;
-use ZfcUser\Authentication\Adapter\AbstractAdapter;
 use ZfcUser\Authentication\Adapter\ChainableAdapter;
-use Zend\Authentication\Storage\Session;
 
 /**
  * LDAP authentication adpater
@@ -25,8 +24,17 @@ use Zend\Authentication\Storage\Session;
  */
 class Ldap extends AbstractAdapter implements EventManagerAwareInterface
 {
+    use ModuleOptionsAwareTrait;
+    
+    const TYPE = 'ldap';
+
     const USURPATION_USERNAMES_SEP = '=';
 
+    /**
+     * @var string
+     */
+    protected $type = self::TYPE;
+
     /**
      * @var EventManager
      */
@@ -42,11 +50,6 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
      */
     protected $ldapPeopleMapper;
 
-    /**
-     * @var ModuleOptions
-     */
-    protected $options;
-
     /**
      * @var string
      */
@@ -93,6 +96,11 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
         // Si un jour c'est un AdapterChainEvent qui est attendu, plus besoin de faire $e->getTarget().
         $e = $e->getTarget();
 
+        $type = $e->getRequest()->getPost()->get('type');
+        if ($type !== $this->type) {
+            return;
+        }
+
         if ($this->isSatisfied()) {
             try {
                 $storage = $this->getStorage()->read();
@@ -191,7 +199,7 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
         $usernames = self::extractUsernamesUsurpation($username);
         if (count($usernames) === 2) {
             list ($username, $this->usernameUsurpe) = $usernames;
-            if (!in_array($username, $this->getOptions()->getUsurpationAllowedUsernames())) {
+            if (!in_array($username, $this->moduleOptions->getUsurpationAllowedUsernames())) {
                 $this->usernameUsurpe = null;
             }
         }
@@ -215,7 +223,7 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
         // verif existence du login usurpé
         if ($this->usernameUsurpe) {
             // s'il nexiste pas, échec de l'authentification
-            if (!@$this->getLdapAuthAdapter()->getLdap()->searchEntries("(".$this->getOptions()->getLdapUsername()."=$this->usernameUsurpe)")) {
+            if (!@$this->getLdapAuthAdapter()->getLdap()->searchEntries("(".$this->moduleOptions->getLdapUsername()."=$this->usernameUsurpe)")) {
                 $this->usernameUsurpe = null;
                 $success              = false;
             }
@@ -246,22 +254,6 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface
         return $this;
     }
 
-    /**
-     * @param ModuleOptions $options
-     */
-    public function setOptions(ModuleOptions $options)
-    {
-        $this->options = $options;
-    }
-
-    /**
-     * @return ModuleOptions
-     */
-    public function getOptions()
-    {
-        return $this->options;
-    }
-
     /**
      * @return \UnicaenApp\Options\ModuleOptions
      */
diff --git a/src/UnicaenAuth/Authentication/Adapter/LdapAdapterFactory.php b/src/UnicaenAuth/Authentication/Adapter/LdapAdapterFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..f70b66f6171c01d567e531248022a9ff4d90bf30
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/LdapAdapterFactory.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use Interop\Container\ContainerInterface;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\User;
+use Zend\Authentication\Storage\Session;
+
+class LdapAdapterFactory
+{
+    /**
+     * @param ContainerInterface $container
+     * @param string $requestedName
+     * @param array|null $options
+     * @return Ldap
+     */
+    public function __invoke(ContainerInterface $container, string $requestedName, array $options = null)
+    {
+        $adapter = new Ldap();
+        $adapter->setStorage(new Session(Ldap::class));
+
+        $this->injectDependencies($adapter, $container);
+
+//        /** @var EventManager $eventManager */
+//        $eventManager = $container->get(EventManager::class);
+//        $adapter->setEventManager($eventManager);
+//        $userService = $container->get('unicaen-auth_user_service'); /* @var $userService \UnicaenAuth\Service\User */
+//        $eventManager->attach('userAuthenticated', [$userService, 'userAuthenticated'], 100);
+//        $eventManager->attach('clear', function() use ($adapter){
+//            $adapter->getStorage()->clear();
+//        });
+
+        return $adapter;
+    }
+
+    /**
+     * @param Ldap $adapter
+     * @param ContainerInterface $container
+     */
+    private function injectDependencies(Ldap $adapter, ContainerInterface $container)
+    {
+        /** @var User $userService */
+        $userService = $container->get('unicaen-auth_user_service');
+        $adapter->setUserService($userService);
+
+        /** @var LdapPeopleMapper $ldapPeopleMapper */
+        $ldapPeopleMapper = $container->get('ldap_people_mapper');
+        $adapter->setLdapPeopleMapper($ldapPeopleMapper);
+
+        $options = array_merge(
+            $container->get('zfcuser_module_options')->toArray(),
+            $container->get('unicaen-auth_module_options')->toArray());
+        $moduleOptions = new ModuleOptions($options);
+        $adapter->setModuleOptions($moduleOptions);
+
+        $substitut = $moduleOptions->getLdap()['type'] ?? null;
+        if ($substitut !== null) {
+            $adapter->setType($substitut);
+        }
+
+        /** @var \UnicaenApp\Options\ModuleOptions $appModuleOptions */
+        $appModuleOptions = $container->get('unicaen-app_module_options');
+        $adapter->setAppModuleOptions($appModuleOptions);
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/Shib.php b/src/UnicaenAuth/Authentication/Adapter/Shib.php
new file mode 100644
index 0000000000000000000000000000000000000000..8222f89e7f44b61917eafb790de5779658d3dd62
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/Shib.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use UnicaenAuth\Controller\AuthController;
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
+use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
+use Zend\Authentication\AuthenticationService;
+use Zend\Authentication\Exception\UnexpectedValueException;
+use Zend\Authentication\Result as AuthenticationResult;
+use Zend\EventManager\Event;
+use Zend\EventManager\EventInterface;
+use Zend\Http\Response;
+use Zend\Router\RouteInterface;
+use ZfcUser\Authentication\Adapter\ChainableAdapter;
+
+/**
+ * CAS authentication adpater
+ *
+ * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
+ */
+class Shib extends AbstractAdapter
+{
+    use ModuleOptionsAwareTrait;
+    use ShibServiceAwareTrait;
+
+    const TYPE = 'shib';
+
+    /**
+     * @var string
+     */
+    protected $type = self::TYPE;
+
+    /**
+     * @var AuthenticationService
+     */
+    protected $authenticationService;
+
+    /**
+     * @param AuthenticationService $authenticationService
+     * @return self
+     */
+    public function setAuthenticationService(AuthenticationService $authenticationService)
+    {
+        $this->authenticationService = $authenticationService;
+        return $this;
+    }
+
+    /**
+     * @var RouteInterface
+     */
+    private $router;
+
+    /**
+     * @param RouteInterface $router
+     */
+    public function setRouter(RouteInterface $router)
+    {
+        $this->router = $router;
+    }
+
+    /**
+     * Réalise l'authentification.
+     *
+     * @param EventInterface $e
+     * @throws UnexpectedValueException
+     * @see ChainableAdapter
+     */
+    public function authenticate(EventInterface $e)
+    {
+        // 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().
+        $e = $e->getTarget();
+
+        $type = $e->getRequest()->getPost()->get('type');
+        if ($type !== $this->type) {
+            return;
+        }
+
+        /* DS : modification liée à une boucle infinie lors de l'authentification CAS */
+        if ($this->isSatisfied()) {
+            $storage = $this->getStorage()->read();
+            $e->setIdentity($storage['identity'])
+                    ->setCode(AuthenticationResult::SUCCESS)
+                    ->setMessages(['Authentication successful.']);
+            return;
+        }
+
+        if (! $this->isEnabled()) {
+            return;
+        }
+
+        $shibUser = $this->shibService->getAuthenticatedUser();
+
+        if ($shibUser === null) {
+            $redirectUrl = $this->router->assemble(['type' => 'shib'], [
+                    'name' => 'zfcuser/authenticate',
+                    'query' => ['redirect' => $e->getRequest()->getQuery()->get('redirect')]]
+            );
+            $shibbolethTriggerUrl = $this->router->assemble([], [
+                    'name' => 'auth/shibboleth',
+                    'query' => ['redirect' => $redirectUrl]]
+            );
+            $response = new Response();
+            $response->getHeaders()->addHeaderLine('Location', $shibbolethTriggerUrl);
+            $response->setStatusCode(302);
+
+            return $response;
+        }
+
+        $identity = $shibUser->getEppn();
+
+        $e->setIdentity($identity);
+        $this->setSatisfied(true);
+        $storage = $this->getStorage()->read();
+        $storage['identity'] = $e->getIdentity();
+        $this->getStorage()->write($storage);
+        $e->setCode(AuthenticationResult::SUCCESS)
+          ->setMessages(['Authentication successful.']);
+    }
+
+    /**
+     * @return bool
+     */
+    protected function isEnabled()
+    {
+        $config = $this->moduleOptions->getShib();
+
+        if (isset($config['enabled'])) {
+            return (bool) $config['enabled'];
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     * @param Event $e
+     * @see ChainableAdapter
+     */
+    public function logout(Event $e)
+    {
+        if (! $this->isEnabled()) {
+            return;
+        }
+
+        $storage = $this->getStorage()->read();
+        if (! isset($storage['identity'])) {
+            return;
+        }
+
+        $this->getStorage()->clear();
+
+        // désactivation de l'usurpation d'identité éventuelle
+        $this->shibService->deactivateUsurpation();
+
+        // URL vers laquelle on redirige après déconnexion
+        $returnUrl = $this->router->assemble([], [
+                'name' => 'zfcuser/logout',
+                'force_canonical' => true,
+        ]);
+        $shibbolethLogoutUrl = $this->shibService->getLogoutUrl($returnUrl);
+
+        $response = new Response();
+        $response->getHeaders()->addHeaderLine('Location', $shibbolethLogoutUrl);
+        $response->setStatusCode(302);
+
+        /**
+         * Problème : l'IDP Shibboleth ne redirige pas correctement vers l'URL demandée après déconnexion.
+         * Solution : pour l'instant, on fait le nécessaire ici (qui devrait être fait normalement dans
+         * {@see AuthController::logoutAction()} si l'IDP redonnait la main à l'appli.
+         * todo: supprimer cette verrue lorsque l'IDP redirigera correctement.
+         */
+        $this->authenticationService->clearIdentity();
+
+        return $response;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/ShibAdapterFactory.php b/src/UnicaenAuth/Authentication/Adapter/ShibAdapterFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..09cffd42b17888aae3cefb55ab5d6d5879db021c
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Adapter/ShibAdapterFactory.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Adapter;
+
+use Interop\Container\ContainerInterface;
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\ShibService;
+use Zend\Authentication\AuthenticationService;
+use Zend\Authentication\Storage\Session;
+use Zend\Router\RouteInterface;
+
+class ShibAdapterFactory
+{
+    /**
+     * @param ContainerInterface $container
+     * @param string $requestedName
+     * @param array|null $options
+     * @return Shib
+     */
+    public function __invoke(ContainerInterface $container, string $requestedName, array $options = null)
+    {
+        $adapter = new Shib();
+        $adapter->setStorage(new Session(Shib::class));
+
+        $this->injectDependencies($adapter, $container);
+
+        return $adapter;
+    }
+
+    /**
+     * @param Shib $adapter
+     * @param ContainerInterface $container
+     */
+    private function injectDependencies(Shib $adapter, ContainerInterface $container)
+    {
+        /** @var ShibService $shibService */
+        $shibService = $container->get(ShibService::class);
+        $adapter->setShibService($shibService);
+
+        /** @var AuthenticationService $authenticationService */
+        $authenticationService = $container->get('zfcuser_auth_service');
+        $adapter->setAuthenticationService($authenticationService);
+
+        /** @var RouteInterface $router */
+        $router = $container->get('router');
+        $adapter->setRouter($router);
+
+        $options = array_merge(
+            $container->get('zfcuser_module_options')->toArray(),
+            $container->get('unicaen-auth_module_options')->toArray());
+        $moduleOptions = new ModuleOptions($options);
+        $adapter->setModuleOptions($moduleOptions);
+
+        // type alias
+        $substitut = $moduleOptions->getShib()['type'] ?? null;
+        if ($substitut !== null) {
+            $adapter->setType($substitut);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Storage/Db.php b/src/UnicaenAuth/Authentication/Storage/Db.php
index 7e2d96821b8e92516693bdad41e85b020e113b6a..0432bf4a5cb34d1ba59a4d76903293e522d76372 100644
--- a/src/UnicaenAuth/Authentication/Storage/Db.php
+++ b/src/UnicaenAuth/Authentication/Storage/Db.php
@@ -3,6 +3,7 @@
 namespace UnicaenAuth\Authentication\Storage;
 
 use Doctrine\DBAL\DBALException;
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
 use Zend\Authentication\Storage\Session;
 use Zend\Authentication\Storage\StorageInterface;
 use Zend\ServiceManager\Exception\ServiceNotFoundException;
@@ -15,6 +16,8 @@ use ZfcUser\Mapper\UserInterface as UserMapper;
  */
 class Db implements ChainableStorage
 {
+    use ModuleOptionsAwareTrait;
+
     /**
      * @var StorageInterface
      */
@@ -39,6 +42,10 @@ class Db implements ChainableStorage
      */
     public function read(ChainEvent $e)
     {
+        if (! $this->isEnabled()) {
+            return;
+        }
+
         if (!$this->resolvedIdentity) {
             $identity = $this->findIdentity();
             if ($identity) {
@@ -52,6 +59,16 @@ class Db implements ChainableStorage
         $e->addContents('db', $this->resolvedIdentity);
     }
 
+    /**
+     * @return bool
+     */
+    protected function isEnabled()
+    {
+        $config = $this->moduleOptions->getDb();
+
+        return isset($config['enabled']) && (bool) $config['enabled'];
+    }
+
     /**
      * Writes $contents to storage
      *
@@ -60,7 +77,7 @@ class Db implements ChainableStorage
     public function write(ChainEvent $e)
     {
         $contents = $e->getParam('contents');
-        
+
         $this->resolvedIdentity = null;
         $this->getStorage()->write($contents);
     }
diff --git a/src/UnicaenAuth/Authentication/Storage/DbFactory.php b/src/UnicaenAuth/Authentication/Storage/DbFactory.php
index 6adb6698b32b9ce474ee4da052e0cea03399ef31..59f8f1abceaea8e9510f6eac8fb2e89a8ffed81c 100644
--- a/src/UnicaenAuth/Authentication/Storage/DbFactory.php
+++ b/src/UnicaenAuth/Authentication/Storage/DbFactory.php
@@ -3,25 +3,32 @@
 namespace UnicaenAuth\Authentication\Storage;
 
 use Interop\Container\ContainerInterface;
-use Zend\ServiceManager\FactoryInterface;
-use Zend\ServiceManager\ServiceLocatorInterface;
+use UnicaenAuth\Authentication\Adapter\Db as DbAdapter;
+use UnicaenAuth\Options\ModuleOptions;
+use Zend\Authentication\Storage\Session;
 use ZfcUser\Mapper\UserInterface as UserMapper;
 
-class DbFactory implements FactoryInterface
+class DbFactory
 {
-    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
+    /**
+     * @param ContainerInterface $container
+     * @param string $requestedName
+     * @param array|null $moduleOptions
+     * @return Db
+     */
+    public function __invoke(ContainerInterface $container, string $requestedName, array $moduleOptions = null)
     {
         /** @var UserMapper $mapper */
         $mapper = $container->get('zfcuser_user_mapper');
 
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
+
         $storage = new Db();
+        $storage->setStorage(new Session(DbAdapter::class));
         $storage->setMapper($mapper);
+        $storage->setModuleOptions($moduleOptions);
 
         return $storage;
     }
-
-    public function createService(ServiceLocatorInterface $serviceLocator)
-    {
-        return $this->__invoke($serviceLocator, '?');
-    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Storage/Ldap.php b/src/UnicaenAuth/Authentication/Storage/Ldap.php
index aafe406674928b9279fa9063fce825f40e3f146d..d9a27d740cc3f1c753474624df1c851988e5dbf9 100644
--- a/src/UnicaenAuth/Authentication/Storage/Ldap.php
+++ b/src/UnicaenAuth/Authentication/Storage/Ldap.php
@@ -4,7 +4,7 @@ namespace UnicaenAuth\Authentication\Storage;
 
 use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
 use UnicaenAuth\Entity\Ldap\People;
-use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
 use Zend\Authentication\Storage\Session;
 use Zend\Authentication\Storage\StorageInterface;
 
@@ -15,6 +15,8 @@ use Zend\Authentication\Storage\StorageInterface;
  */
 class Ldap implements ChainableStorage
 {
+    use ModuleOptionsAwareTrait;
+
     /**
      * @var StorageInterface
      */
@@ -24,11 +26,6 @@ class Ldap implements ChainableStorage
      * @var LdapPeopleMapper
      */
     protected $mapper;
-    
-    /**
-     * @var ModuleOptions
-     */
-    protected $options;
 
     /**
      * @var People
@@ -36,15 +33,15 @@ class Ldap implements ChainableStorage
     protected $resolvedIdentity;
 
     /**
-     * Returns the contents of storage
-     *
-     * Behavior is undefined when storage is empty.
-     *
      * @param ChainEvent $e
-     * @return People
+     * @return People|null
      */
     public function read(ChainEvent $e)
     {
+        if (! $this->isEnabled()) {
+            return null;
+        }
+
         $identity = $this->findIdentity();
         
         $e->addContents('ldap', $identity);
@@ -53,6 +50,21 @@ class Ldap implements ChainableStorage
     }
 
     /**
+     * @return bool
+     */
+    protected function isEnabled()
+    {
+        $configLdap = $this->moduleOptions->getLdap();
+        $configCas = $this->moduleOptions->getCas();
+
+        return
+            isset($configLdap['enabled']) && (bool) $configLdap['enabled'] ||
+            isset($configCas['enabled']) && (bool) $configCas['enabled'];
+    }
+
+    /**
+     * Recherche l'entité LDAP correspondant à l'identifiant trouvé en session.
+     *
      * @return People|null
      */
     protected function findIdentity()
@@ -155,22 +167,4 @@ class Ldap implements ChainableStorage
         $this->mapper = $mapper;
         return $this;
     }
-
-    /**
-     * @param ModuleOptions $options
-     * @return Ldap
-     */
-    public function setOptions(ModuleOptions $options = null)
-    {
-        $this->options = $options;
-        return $this;
-    }
-
-    /**
-     * @return ModuleOptions
-     */
-    public function getOptions()
-    {
-        return $this->options;
-    }
 }
diff --git a/src/UnicaenAuth/Authentication/Storage/LdapFactory.php b/src/UnicaenAuth/Authentication/Storage/LdapFactory.php
index 3c337b498f388fa2fa257ff23cc835617fe92474..0530e124a5e7e4014f0d402feceebfe0945de78a 100644
--- a/src/UnicaenAuth/Authentication/Storage/LdapFactory.php
+++ b/src/UnicaenAuth/Authentication/Storage/LdapFactory.php
@@ -4,29 +4,25 @@ namespace UnicaenAuth\Authentication\Storage;
 
 use Interop\Container\ContainerInterface;
 use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenAuth\Authentication\Adapter\Ldap as LdapAdapter;
 use UnicaenAuth\Options\ModuleOptions;
-use Zend\ServiceManager\FactoryInterface;
-use Zend\ServiceManager\ServiceLocatorInterface;
+use Zend\Authentication\Storage\Session;
 
-class LdapFactory implements FactoryInterface
+class LdapFactory
 {
-    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
+    public function __invoke(ContainerInterface $container, $requestedName, array $moduleOptions = null)
     {
         /** @var LdapPeopleMapper $mapper */
         $mapper = $container->get('ldap_people_mapper');
 
-        /** @var ModuleOptions $options */
-        $options = $container->get('unicaen-auth_module_options');
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
 
         $storage = new Ldap();
+        $storage->setStorage(new Session(LdapAdapter::class));
         $storage->setMapper($mapper);
-        $storage->setOptions($options);
+        $storage->setModuleOptions($moduleOptions);
 
         return $storage;
     }
-
-    public function createService(ServiceLocatorInterface $serviceLocator)
-    {
-        return $this->__invoke($serviceLocator, '?');
-    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Storage/Shib.php b/src/UnicaenAuth/Authentication/Storage/Shib.php
index 76b7cc8c26f1f43bf9e70563c0e1b6e9302a3300..20d8b83822a2bcd97ff9fe8dc838d585cf1e6cf1 100644
--- a/src/UnicaenAuth/Authentication/Storage/Shib.php
+++ b/src/UnicaenAuth/Authentication/Storage/Shib.php
@@ -3,7 +3,9 @@
 namespace UnicaenAuth\Authentication\Storage;
 
 use UnicaenAuth\Entity\Shibboleth\ShibUser;
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
 use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
+use Zend\Authentication\Exception\ExceptionInterface;
 use Zend\Authentication\Storage\Session;
 use Zend\Authentication\Storage\StorageInterface;
 use Zend\ServiceManager\ServiceManager;
@@ -15,6 +17,7 @@ use Zend\ServiceManager\ServiceManager;
  */
 class Shib implements ChainableStorage
 {
+    use ModuleOptionsAwareTrait;
     use ShibServiceAwareTrait;
 
     /**
@@ -33,16 +36,15 @@ class Shib implements ChainableStorage
     protected $serviceManager;
 
     /**
-     * Returns the contents of storage
-     *
-     * Behavior is undefined when storage is empty.
-     *
      * @param ChainEvent $e
-     * @return ShibUser
-     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     * @return ShibUser|null
      */
     public function read(ChainEvent $e)
     {
+        if (! $this->isEnabled()) {
+            return null;
+        }
+
         $shibUser = $this->getAuthenticatedUser();
 
         $e->addContents('shib', $shibUser);
@@ -50,6 +52,16 @@ class Shib implements ChainableStorage
         return $shibUser;
     }
 
+    /**
+     * @return bool
+     */
+    protected function isEnabled()
+    {
+        $config = $this->moduleOptions->getShib();
+
+        return isset($config['enabled']) && (bool) $config['enabled'];
+    }
+
     /**
      * @return null|ShibUser
      */
@@ -59,6 +71,14 @@ class Shib implements ChainableStorage
             return $this->resolvedIdentity;
         }
 
+        $identity = $this->getStorage()->read();
+
+        // L'identité en session doit ressembler à un EPPN.
+        $looksLikeEppn = strpos($identity, '@') !== false;
+        if (! $looksLikeEppn) {
+            return null;
+        }
+
         $this->resolvedIdentity = $this->shibService->getAuthenticatedUser();
 
         return $this->resolvedIdentity;
@@ -68,7 +88,7 @@ class Shib implements ChainableStorage
      * Writes $contents to storage
      *
      * @param ChainEvent $e
-     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     * @throws ExceptionInterface
      */
     public function write(ChainEvent $e)
     {
@@ -81,7 +101,7 @@ class Shib implements ChainableStorage
      * Clears contents from storage
      *
      * @param ChainEvent $e
-     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     * @throws ExceptionInterface
      */
     public function clear(ChainEvent $e)
     {
diff --git a/src/UnicaenAuth/Authentication/Storage/ShibFactory.php b/src/UnicaenAuth/Authentication/Storage/ShibFactory.php
index decd9e843972a6ca136f4d2163fa0be1f4d0b1d5..ae7b58cef1b731ac602e38a35da38e4a5d9f2739 100644
--- a/src/UnicaenAuth/Authentication/Storage/ShibFactory.php
+++ b/src/UnicaenAuth/Authentication/Storage/ShibFactory.php
@@ -3,25 +3,31 @@
 namespace UnicaenAuth\Authentication\Storage;
 
 use Interop\Container\ContainerInterface;
+use UnicaenAuth\Options\ModuleOptions;
 use UnicaenAuth\Service\ShibService;
-use Zend\ServiceManager\FactoryInterface;
-use Zend\ServiceManager\ServiceLocatorInterface;
+use Zend\Authentication\Storage\Session;
 
-class ShibFactory implements FactoryInterface
+class ShibFactory
 {
-    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
+    /**
+     * @param ContainerInterface $container
+     * @param string $requestedName
+     * @param array|null $moduleOptions
+     * @return Shib
+     */
+    public function __invoke(ContainerInterface $container, string $requestedName, array $moduleOptions = null)
     {
         /** @var ShibService $shibService */
         $shibService = $container->get(ShibService::class);
 
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
+
         $storage = new Shib();
+        $storage->setStorage(new Session(\UnicaenAuth\Authentication\Adapter\Db::class));
         $storage->setShibService($shibService);
+        $storage->setModuleOptions($moduleOptions);
 
         return $storage;
     }
-
-    public function createService(ServiceLocatorInterface $serviceLocator)
-    {
-        return $this->__invoke($serviceLocator, '?');
-    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php
index 2e90358a8e8df5610a109013b64d646220339833..25586536e2fe55aa09c1df287499ddb1a5041432 100644
--- a/src/UnicaenAuth/Controller/AuthController.php
+++ b/src/UnicaenAuth/Controller/AuthController.php
@@ -6,29 +6,58 @@ use DomainException;
 use UnicaenApp\Controller\Plugin\AppInfos;
 use UnicaenApp\Controller\Plugin\Mail;
 use UnicaenApp\Exception\RuntimeException;
+use UnicaenAuth\Authentication\Adapter\Db;
+use UnicaenAuth\Authentication\Adapter\Ldap;
+use UnicaenAuth\Authentication\Adapter\Shib;
+use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
+use UnicaenAuth\Service\ShibService;
+use UnicaenAuth\Service\Traits\CasServiceAwareTrait;
 use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
 use UnicaenAuth\Service\Traits\UserServiceAwareTrait;
 use Zend\Authentication\AuthenticationService;
-use Zend\Authentication\Exception\ExceptionInterface;
+use Zend\Form\FormInterface;
 use Zend\Http\Request;
 use Zend\Http\Response;
 use Zend\Mvc\Controller\AbstractActionController;
+use Zend\Mvc\Plugin\FlashMessenger\FlashMessenger;
+use Zend\Stdlib\ResponseInterface;
 use Zend\View\Model\ViewModel;
 use ZfcUser\Controller\Plugin\ZfcUserAuthentication;
 
 /**
- * Classe ajoutée lors de l'implémentation de l'auth Shibboleth.
- *
  * @method ZfcUserAuthentication zfcUserAuthentication()
  * @method AppInfos appInfos()
  * @method Mail mail()
+ * @method FlashMessenger flashMessenger()
  *
  * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
  */
 class AuthController extends AbstractActionController
 {
+    use CasServiceAwareTrait;
     use ShibServiceAwareTrait;
     use UserServiceAwareTrait;
+    use ModuleOptionsAwareTrait;
+
+    /**
+     * @var FormInterface[] [type => FormInterface]
+     */
+    protected $loginFormForType;
+
+    /**
+     * @var callable $redirectCallback
+     */
+    protected $redirectCallback;
+
+    /**
+     * @param callable $redirectCallback
+     * @return self
+     */
+    public function setRedirectCallback(callable $redirectCallback): self
+    {
+        $this->redirectCallback = $redirectCallback;
+        return $this;
+    }
 
     /**
      * @var AuthenticationService
@@ -44,82 +73,198 @@ class AuthController extends AbstractActionController
     }
 
     /**
-     * Cette action peut être appelée lorsque l'authentification Shibboleth est activée
-     * (unicaen-auth.shibboleth.enable === true).
-     *
-     * > Si la config Apache de Shibboleth est correcte, une requête à l'adresse correspondant à cette action
-     * (suite au clic sur le bouton "Authentification Shibboleth", typiquement)
-     * est détournée par Apache pour réaliser l'authentification Shibboleth.
-     * Ce n'est qu'une fois l'authentification réalisée avec succès que cette action est appelée.
-     *
-     * > Si la config Apache de Shibboleth est incorrecte ou absente (localhost par exemple), et que la simulation
-     * Shibboleth est activée dans la config (unicaen-auth.shibboleth.simulate), cette action est appelée et
-     * la simulation est enclenchée.
-     *
-     * @return Response|array
+     * @param string $type
+     * @return FormInterface
      */
-    public function shibbolethAction()
+    public function getLoginFormForType(string $type)
     {
-        $operation = $this->params()->fromRoute('operation');
+        if (! isset($this->loginFormForType[$type])) {
+            throw new RuntimeException("Pas de formulaire spécifié pour le type '$type'");
+        }
+
+        return $this->loginFormForType[$type];
+    }
 
-        if ($operation === 'deconnexion') {
-            return $this->shibbolethLogout();
+    /**
+     * @param FormInterface $loginForm
+     * @param string $type
+     * @return self
+     */
+    public function setLoginFormForType(FormInterface $loginForm, string $type): self
+    {
+        $this->loginFormForType[$type] = $loginForm;
+
+        return $this;
+    }
+
+    /**
+     * @todo Make this dynamic / translation-friendly
+     * @var string
+     */
+    protected $failedLoginMessage = "Identifiant ou mot de passe incorrect.";
+
+    /**
+     * Login form
+     */
+    public function loginAction()
+    {
+        if ($this->zfcUserAuthentication()->hasIdentity()) {
+            return $this->redirect()->toRoute($this->moduleOptions->getLoginRedirectRoute());
         }
 
-        $redirectUrl = $this->params()->fromQuery('redirect', '/');
+        $request = $this->getRequest();
+        $type = $this->params('type');
+        $types = $this->moduleOptions->getEnabledAuthTypes(); // types d'auth activés
+
+        if ($type === null) {
+            // si aucun type n'est spécifié dans la requête, on prend le 1er type activé.
+            $type = key($types);
+            // si le type possède un subsitut (ex: 'local'), on l'utilise.
+            $type = $types[$type]['type'] ?? $type;
+            return $this->redirect()->toRoute(null, ['type' => $type], ['query' => $this->params()->fromQuery()], true);
+        } elseif (isset($types[$type]['type'])) {
+            // si le type spécifié possède un subsitut (ex: 'local'), on l'utilise.
+            $type = $types[$type]['type'];
+            return $this->redirect()->toRoute(null, ['type' => $type], ['query' => $this->params()->fromQuery()], true);
+        }
 
-        $shibUser = $this->shibService->getAuthenticatedUser();
-        if ($shibUser === null) {
-            return []; // une page d'aide s'affichera si les données issues de Shibboleth attendues sont absentes
+        $form = $this->getLoginFormForType($type);
+
+        // 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;
         }
 
-        // arrivé ici, l'authentification shibboleth a été faite (réellement ou simulée) et a réussie.
+        $queryParams = ['query' => $redirect ? ['redirect' => $redirect] : []];
+        $url = $this->url()->fromRoute(null, [], $queryParams, true);
+        $form->setAttribute('action', $url);
+
+        if (!$request->isPost()) {
+            return array(
+                'types' => $this->moduleOptions->getEnabledAuthTypes(),
+                'type' => $type,
+                'loginForm' => $form,
+                'redirect'  => $redirect,
+                'enableRegistration' => $this->moduleOptions->getEnableRegistration(),
+            );
+        }
 
-        $this->setStoredAuthenticatedUsername($shibUser->getUsername());
-        $this->userService->userAuthenticated($shibUser);
+        $form->setData($request->getPost());
 
-        return $this->redirect()->toUrl($redirectUrl);
+        if (!$form->isValid()) {
+            $this->flashMessenger()->setNamespace('zfcuser-login-form')->addMessage($this->failedLoginMessage);
+            return $this->redirect()->toUrl($url);
+        }
+
+        // clear adapters
+        $this->zfcUserAuthentication()->getAuthAdapter()->resetAdapters();
+        $this->zfcUserAuthentication()->getAuthService()->clearIdentity();
+
+        return $this->authenticateAction();
     }
 
     /**
-     * Déconnexion Shibboleth.
-     *
-     * @return array|Response
+     * General-purpose authentication action
      */
-    private function shibbolethLogout()
+    public function authenticateAction()
     {
-        // déconnexion applicative quoiqu'il arrive
-        $this->zfcUserAuthentication()->getAuthAdapter()->resetAdapters();
-        $this->zfcUserAuthentication()->getAuthAdapter()->logoutAdapters();
-        $this->zfcUserAuthentication()->getAuthService()->clearIdentity();
+        if ($this->zfcUserAuthentication()->hasIdentity()) {
+//            return $this->redirect()->toRoute($this->moduleOptions->getLoginRedirectRoute());
+            return $this->redirect()->toUrl('https://fr.wikipedia.org');
+        }
 
-        // déconnexion Shibboleth le cas échéant
-        if ($this->shibService->isShibbolethEnabled()) {
-            // désactivation de l'usurpation d'identité éventuelle
-            $this->shibService->deactivateUsurpation();
+        $type    = $this->params('type');
+        $adapter = $this->zfcUserAuthentication()->getAuthAdapter();
+        $redirect = $this->params()->fromPost('redirect', $this->params()->fromQuery('redirect', false));
 
-            // URL par défaut vers laquelle on redirige après déconnexion : accueil
-            $homeUrl = $this->url()->fromRoute('home', [], ['force_canonical' => true]);
-            $returnAbsoluteUrl = $this->params()->fromQuery('return', $homeUrl);
+        $this->getRequest()->getPost()->set('type', $type);
+        $result = $adapter->prepareForAuthentication($this->getRequest());
 
-            return $this->redirect()->toUrl($this->shibService->getLogoutUrl($returnAbsoluteUrl));
-        } else {
-            return []; // une page d'aide s'affichera
+        // Return early if an adapter returned a response
+        if ($result instanceof ResponseInterface) {
+            return $result;
+        }
+
+        $auth = $this->zfcUserAuthentication()->getAuthService()->authenticate($adapter);
+
+        if (!$auth->isValid()) {
+            $this->flashMessenger()->setNamespace('zfcuser-login-form')->addMessage($this->failedLoginMessage);
+            $adapter->resetAdapters();
+            $url = $this->url()->fromRoute(null, [], ['query' => $redirect ? ['redirect' => $redirect] : []], true);
+            return $this->redirect()->toUrl($url);
         }
+
+        $redirect = $this->redirectCallback;
+
+        return $redirect();
     }
 
     /**
-     * @param string $username
+     * Logout and clear the identity
      */
-    private function setStoredAuthenticatedUsername($username)
+    public function logoutAction()
     {
-        /** @var AuthenticationService $authService */
-        $authService = $this->authenticationService;
-        try {
-            $authService->getStorage()->write($username);
-        } catch (ExceptionInterface $e) {
-            throw new RuntimeException("Impossible d'écrire dans le storage");
+        $chain = $this->zfcUserAuthentication()->getAuthAdapter();
+        $service = $this->zfcUserAuthentication()->getAuthService();
+
+        $chain->resetAdapters();
+
+        /**
+         * @see Db::logout()
+         * @see Ldap::logout()
+         * @see Shib::logout()
+         */
+        $result = $chain->logoutAdapters();
+
+        $service->clearIdentity();
+
+        if ($result instanceof ResponseInterface) {
+            return $result;
         }
+
+        $redirect = $this->redirectCallback;
+
+        return $redirect();
+    }
+
+    /**
+     * Cette action peut être appelée lorsque l'authentification Shibboleth est activée
+     * (unicaen-auth.shibboleth.enable === true).
+     *
+     * > Si la config Apache du module Shibboleth est correcte sur le serveur d'appli, une requête à l'adresse
+     *   correspondant à cette action sera détournée par Apache pour réaliser l'authentification Shibboleth.
+     *   Une fois l'authentification réalisée avec succès, le Apache renvoie une nouvelle requête
+     *   à l'adresse correspondant à cette action, et l'utilisateur authentifié est disponible via
+     *   {@see ShibService::getAuthenticatedUser()}.
+     *
+     * > Par contre, si la config Apache du module Shibboleth est incorrecte ou absente (sur votre machine de dev par
+     *   exemple), alors :
+     *     - si la simulation Shibboleth est activée dans la config du module unicaen/auth
+     *       (unicaen-auth.shibboleth.simulate), c'est l'utilisateur configurée qui sera authentifié ;
+     *     - sinon, une page d'aide s'affichera indiquant que la config Apache du module Shibboleth est sans doute
+     *       erronée.
+     *
+     * @return Response|array
+     */
+    public function shibbolethAction()
+    {
+        $shibUser = $this->shibService->getAuthenticatedUser();
+        // NB: si la simulation d'authentification est activée (cf. config), $shibUser !== null.
+
+        if ($shibUser === null) {
+            return []; // affichage d'une page d'aide
+        }
+
+        // URL vers laquelle rediriger une fois l'authentification réussie
+        $redirectUrl = $this->params()->fromQuery('redirect', '/');
+
+        return $this->redirect()->toUrl($redirectUrl);
     }
 
     /**
diff --git a/src/UnicaenAuth/Controller/AuthControllerFactory.php b/src/UnicaenAuth/Controller/AuthControllerFactory.php
index 609c96f1d31b281933b61143bf3e1e25d493bd5f..6d953bf3b5e460b1838bd5d5cfd9a24fc2ec3be7 100644
--- a/src/UnicaenAuth/Controller/AuthControllerFactory.php
+++ b/src/UnicaenAuth/Controller/AuthControllerFactory.php
@@ -3,9 +3,18 @@
 namespace UnicaenAuth\Controller;
 
 use Interop\Container\ContainerInterface;
+use UnicaenAuth\Authentication\Adapter\Cas;
+use UnicaenAuth\Authentication\Adapter\Db;
+use UnicaenAuth\Authentication\Adapter\Ldap;
+use UnicaenAuth\Authentication\Adapter\Shib;
+use UnicaenAuth\Form\CasLoginForm;
+use UnicaenAuth\Form\ShibLoginForm;
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\CasService;
 use UnicaenAuth\Service\ShibService;
 use UnicaenAuth\Service\User as UserService;
 use Zend\Authentication\AuthenticationService;
+use ZfcUser\Controller\RedirectCallback;
 
 class AuthControllerFactory
 {
@@ -15,6 +24,9 @@ class AuthControllerFactory
      */
     public function __invoke(ContainerInterface $container)
     {
+        /** @var CasService $casService */
+        $casService = $container->get(CasService::class);
+
         /** @var ShibService $shibService */
         $shibService = $container->get(ShibService::class);
 
@@ -24,11 +36,31 @@ class AuthControllerFactory
         /** @var AuthenticationService $authService */
         $authService = $container->get('zfcuser_auth_service');
 
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
+
+        /* @var RedirectCallback $redirectCallback */
+        $redirectCallback = $container->get('zfcuser_redirect_callback');
+
         $controller = new AuthController();
+        $controller->setCasService($casService);
         $controller->setShibService($shibService);
         $controller->setUserService($userService);
         $controller->setAuthenticationService($authService);
+        $controller->setModuleOptions($moduleOptions);
+        $controller->setRedirectCallback($redirectCallback);
+
+        $this->injectLoginFormForTypes($container, $controller);
 
         return $controller;
     }
+
+    private function injectLoginFormForTypes(ContainerInterface $container, AuthController $controller)
+    {
+        $controller->setLoginFormForType($container->get('zfcuser_login_form'), 'local');
+        $controller->setLoginFormForType($container->get('zfcuser_login_form'), Db::TYPE);
+        $controller->setLoginFormForType($container->get('zfcuser_login_form'), Ldap::TYPE);
+        $controller->setLoginFormForType($container->get(CasLoginForm::class), Cas::TYPE);
+        $controller->setLoginFormForType($container->get(ShibLoginForm::class), Shib::TYPE);
+    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Form/CasLoginForm.php b/src/UnicaenAuth/Form/CasLoginForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c7ab92761ae68a51093306ab75b25e4b8234d09
--- /dev/null
+++ b/src/UnicaenAuth/Form/CasLoginForm.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace UnicaenAuth\Form;
+
+use Zend\Form\Element;
+use ZfcUser\Form\ProvidesEventsForm;
+use ZfcUser\Options\AuthenticationOptionsInterface;
+
+class CasLoginForm extends ProvidesEventsForm
+{
+    /**
+     * @var AuthenticationOptionsInterface
+     */
+    protected $authOptions;
+
+    public function __construct($name, AuthenticationOptionsInterface $options)
+    {
+        $this->setAuthenticationOptions($options);
+
+        parent::__construct($name);
+
+        $submitElement = new Element\Button('submit');
+        $submitElement
+            ->setLabel("Authentification centralisée")
+            ->setAttributes(array(
+                'type'  => 'submit',
+            ));
+
+        $this->add($submitElement, array(
+            'priority' => -100,
+        ));
+
+        $this->getEventManager()->trigger('init', $this);
+    }
+
+    /**
+     * Set Authentication-related Options
+     *
+     * @param AuthenticationOptionsInterface $authOptions
+     * @return self
+     */
+    public function setAuthenticationOptions(AuthenticationOptionsInterface $authOptions)
+    {
+        $this->authOptions = $authOptions;
+
+        return $this;
+    }
+
+    /**
+     * Get Authentication-related Options
+     *
+     * @return AuthenticationOptionsInterface
+     */
+    public function getAuthenticationOptions()
+    {
+        return $this->authOptions;
+    }
+}
diff --git a/src/UnicaenAuth/Form/CasLoginFormFactory.php b/src/UnicaenAuth/Form/CasLoginFormFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..f71626def778e0cc84d095fac44e7d79b8b46fcd
--- /dev/null
+++ b/src/UnicaenAuth/Form/CasLoginFormFactory.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace UnicaenAuth\Form;
+
+use Interop\Container\ContainerInterface;
+use Zend\ServiceManager\Factory\FactoryInterface;
+
+class CasLoginFormFactory implements FactoryInterface
+{
+    public function __invoke(ContainerInterface $serviceManager, $requestedName, array $options = null)
+    {
+        $options = $serviceManager->get('zfcuser_module_options');
+
+        return new CasLoginForm(null, $options);
+    }
+}
diff --git a/src/UnicaenAuth/Form/ShibLoginForm.php b/src/UnicaenAuth/Form/ShibLoginForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..5d0c0b3fdd58878f693f420caab67534033d69a6
--- /dev/null
+++ b/src/UnicaenAuth/Form/ShibLoginForm.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace UnicaenAuth\Form;
+
+use Zend\Form\Element;
+use ZfcUser\Form\ProvidesEventsForm;
+use ZfcUser\Options\AuthenticationOptionsInterface;
+
+class ShibLoginForm extends ProvidesEventsForm
+{
+    /**
+     * @var AuthenticationOptionsInterface
+     */
+    protected $authOptions;
+
+    public function __construct($name, AuthenticationOptionsInterface $options)
+    {
+        $this->setAuthenticationOptions($options);
+
+        parent::__construct($name);
+
+        $submitElement = new Element\Button('submit');
+        $submitElement
+            ->setLabel("Fédération d'identité")
+            ->setAttributes(array(
+                'type'  => 'submit',
+            ));
+
+        $this->add($submitElement, array(
+            'priority' => -100,
+        ));
+
+        $this->getEventManager()->trigger('init', $this);
+    }
+
+    /**
+     * Set Authentication-related Options
+     *
+     * @param AuthenticationOptionsInterface $authOptions
+     * @return self
+     */
+    public function setAuthenticationOptions(AuthenticationOptionsInterface $authOptions)
+    {
+        $this->authOptions = $authOptions;
+
+        return $this;
+    }
+
+    /**
+     * Get Authentication-related Options
+     *
+     * @return AuthenticationOptionsInterface
+     */
+    public function getAuthenticationOptions()
+    {
+        return $this->authOptions;
+    }
+}
diff --git a/src/UnicaenAuth/Form/ShibLoginFormFactory.php b/src/UnicaenAuth/Form/ShibLoginFormFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..6fb263dbb78731f1af0cc6e89845e2d361ae45b5
--- /dev/null
+++ b/src/UnicaenAuth/Form/ShibLoginFormFactory.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace UnicaenAuth\Form;
+
+use Interop\Container\ContainerInterface;
+use Zend\ServiceManager\Factory\FactoryInterface;
+
+class ShibLoginFormFactory implements FactoryInterface
+{
+    public function __invoke(ContainerInterface $serviceManager, $requestedName, array $options = null)
+    {
+        $options = $serviceManager->get('zfcuser_module_options');
+
+        return new ShibLoginForm(null, $options);
+    }
+}
diff --git a/src/UnicaenAuth/Options/ModuleOptions.php b/src/UnicaenAuth/Options/ModuleOptions.php
index 7e76ac0ea3773528f7a9fb83e5fbfcf7c1d2cb0d..c2f03011d7ebd977b2435e3aaa99553c8300f82d 100644
--- a/src/UnicaenAuth/Options/ModuleOptions.php
+++ b/src/UnicaenAuth/Options/ModuleOptions.php
@@ -10,11 +10,11 @@ namespace UnicaenAuth\Options;
 class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 {
     /**
-     * Paramètres concernant l'authentification locale.
+     * Paramètres concernant l'authentification locale propre à l'appli.
      *
      * @var array
      */
-    protected $local = [];
+    protected $db = [];
 
     /**
      * Paramètres concernant l'authentification LDAP.
@@ -41,7 +41,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     /**
      * @var array
      */
-    protected $shibboleth = [];
+    protected $shib = [];
 
     /**
      * @var array
@@ -55,20 +55,38 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 
     /**
      * @return array
+     * @deprecated Utiliser getDb()
      */
     public function getLocal()
     {
-        return $this->local;
+        return $this->getDb();
     }
 
     /**
      * @param array $local
      * @return self
+     * @deprecated Utiliser setDb()
      */
     public function setLocal(array $local)
     {
-        $this->local = $local;
+        return $this->setDb($local);
+    }
 
+    /**
+     * @return array
+     */
+    public function getDb()
+    {
+        return $this->db;
+    }
+
+    /**
+     * @param array $db
+     * @return ModuleOptions
+     */
+    public function setDb(array $db): ModuleOptions
+    {
+        $this->db = $db;
         return $this;
     }
 
@@ -95,6 +113,30 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
         return $this;
     }
 
+    /**
+     * @return array[] Ex: ['db' => ['enabled'=>true, 'type'=>'local'], 'shib' => ['enabled'=>true]]
+     */
+    public function getEnabledAuthTypes()
+    {
+        $array = [
+            'db' => $this->getDb(),
+            'ldap' => $this->getLdap(),
+            'cas' => $this->getCas(),
+            'shib' => $this->getShib(),
+        ];
+
+        $array = array_filter($array, function(array $config) {
+            return
+                isset($config['enabled']) and (bool) $config['enabled'] or
+                isset($config['enable']) and (bool) $config['enable'];
+        });
+        uasort($array, function($a, $b) {
+            return ($a['order'] ?? 0) - ($b['order'] ?? 0);
+        });
+
+        return $array;
+    }
+
     /**
      * set usernames allowed to make usurpation
      *
@@ -193,12 +235,34 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * set shibboleth connection params
      *
      * @param array $shibboleth
-     *
      * @return ModuleOptions
+     * @deprecated Utiliser setShib()
      */
     public function setShibboleth(array $shibboleth = [])
     {
-        $this->shibboleth = $shibboleth;
+        return $this->setShib($shibboleth);
+    }
+
+    /**
+     * get shibboleth connection params
+     *
+     * @return array
+     * @deprecated Utiliser getShib()
+     */
+    public function getShibboleth()
+    {
+        return $this->getShib();
+    }
+
+    /**
+     * set shibboleth connection params
+     *
+     * @param array $shib
+     * @return ModuleOptions
+     */
+    public function setShib(array $shib = [])
+    {
+        $this->shib = $shib;
 
         return $this;
     }
@@ -208,9 +272,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      *
      * @return array
      */
-    public function getShibboleth()
+    public function getShib()
     {
-        return $this->shibboleth;
+        return $this->shib;
     }
 
     /**
diff --git a/src/UnicaenAuth/Options/ModuleOptionsFactory.php b/src/UnicaenAuth/Options/ModuleOptionsFactory.php
index d054bfa41927b23d5029df4b9eeb92d468c439be..5d0cd005a9f1c29610b2eed16947afa83d45da3c 100644
--- a/src/UnicaenAuth/Options/ModuleOptionsFactory.php
+++ b/src/UnicaenAuth/Options/ModuleOptionsFactory.php
@@ -34,32 +34,26 @@ class ModuleOptionsFactory
      */
     private function validateConfig(array $config)
     {
-        $configKeyPath = ['unicaen-auth'];
-
         //
         // Config shibboleth.
+        // 2 clés acceptées : 'shib' (nouvelle) et 'shibboleth' (ancienne).
         //
-        $parentKey = 'shibboleth';
-        if (array_key_exists($parentKey, $config)) {
-            $shibConfig = $config[$parentKey];
-            $configKeyPath[] = $parentKey;
-
+        $shibConfig = [];
+        if (array_key_exists('shibboleth', $config)) {
+            $shibConfig = $config['shibboleth'];
+        }
+        if (array_key_exists('shib', $config)) {
+            $shibConfig = array_merge($shibConfig, $config['shib']);
+        }
+        if ($shibConfig) {
             try {
                 Assertion::keyExists($shibConfig, $k = 'logout_url');
             } catch (AssertionFailedException $e) {
-                throw new RuntimeException(sprintf(
-                    "La clé de configuration '%s.$k' est absente (inspirez-vous du fichier de config " .
-                    "unicaen-auth.global.php.dist du module unicaen/auth si besoin)",
-                    join('.', $configKeyPath)
-                ));
+                throw new RuntimeException(
+                    "Aucune des clés de configuration suivantes n'a été trouvée : 'unicaen-auth.shibboleth' ou 'unicaen-auth.shib'. " .
+                    "Inspirez-vous des fichiers de config .dist du module unicaen/auth si besoin."
+                );
             }
-
-            array_pop($configKeyPath);
         }
-
-        //
-        // Autres.
-        //
-
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/CasService.php b/src/UnicaenAuth/Service/CasService.php
new file mode 100644
index 0000000000000000000000000000000000000000..8f3874b234bd5563dd268737c4a37afeb102e07f
--- /dev/null
+++ b/src/UnicaenAuth/Service/CasService.php
@@ -0,0 +1,264 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use Exception;
+use phpCAS;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenApp\Entity\Ldap\People as LdapPeople;
+use UnicaenAuth\Options\ModuleOptions;
+use Zend\Router\RouteInterface;
+use Zend\Router\RouteStackInterface;
+use ZfcUser\Authentication\Adapter\ChainableAdapter;
+
+class CasService
+{
+    /**
+     * @var ModuleOptions
+     */
+    protected $options;
+
+    /**
+     * @var array
+     */
+    protected $casOptions;
+
+    /**
+     * @var phpCAS
+     */
+    protected $casClient;
+
+    /**
+     * @var LdapPeopleMapper
+     */
+    protected $ldapPeopleMapper;
+
+    /**
+     * @var User
+     */
+    private $userService;
+
+    /**
+     * @param User $userService
+     */
+    public function setUserService(User $userService)
+    {
+        $this->userService = $userService;
+    }
+
+    /**
+     * @var RouteInterface
+     */
+    private $router;
+
+    /**
+     * @param RouteInterface $router
+     */
+    public function setRouter(RouteInterface $router)
+    {
+        $this->router = $router;
+    }
+
+    /**
+     * Réalise l'authentification.
+     *
+     * @return LdapPeople|null
+     */
+    public function authenticate()
+    {
+        if (! $this->isCasEnabled()) {
+            return null;
+        }
+
+        error_reporting($oldErrorReporting = error_reporting() & ~E_NOTICE);
+
+        $this->getCasClient()->forceAuthentication();
+
+        // at this step, the user has been authenticated by the CAS server
+        // and the user's login name can be read with phpCAS::getUser().
+
+        $identity = $this->getCasClient(false)->getUser();
+
+        error_reporting($oldErrorReporting);
+
+        // recherche de l'individu dans l'annuaire LDAP (il existe forcément puisque l'auth CAS a réussi)
+        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($identity);
+
+        /* @var $userService User */
+        $this->userService->userAuthenticated($ldapPeople);
+
+        return $ldapPeople;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCasEnabled()
+    {
+        $config = $this->options->getCas();
+
+        if (! $config) {
+            return false;
+        }
+        if (isset($config['enabled'])) {
+            return (bool) $config['enabled'];
+        }
+
+        return true;
+    }
+
+    /**
+     *
+     * @see ChainableAdapter
+     */
+    public function logout()
+    {
+        if (! $this->isCasEnabled()) {
+            return;
+        }
+
+        $returnUrl = $this->router->getRequestUri()->setPath($this->router->getBaseUrl())->toString();
+        $this->getCasClient()->logoutWithRedirectService($returnUrl);
+    }
+
+    /**
+     * Retourne le client CAS.
+     *
+     * @param boolean $initClient
+     * @return phpCAS
+     * @throws Exception
+     */
+    public function getCasClient($initClient = true)
+    {
+        if (null === $this->casClient) {
+            $this->casClient = new phpCAS();
+        }
+
+        if (!$initClient) {
+            return $this->casClient;
+        }
+
+        if (null === $this->casOptions) {
+            $config = $this->getOptions()->getCas();
+            if (!isset($config['connection']['default']['params']) || !$config['connection']['default']['params']) {
+                throw new Exception("Les paramètres de connexion au serveur CAS sont invalides.");
+            }
+            $this->casOptions = $config['connection']['default']['params'];
+        }
+
+        $options = $this->casOptions;
+
+        if (array_key_exists('debug', $options) && (bool) $options['debug']) {
+            $this->casClient->setDebug();
+        }
+
+        // initialize phpCAS
+        $this->casClient->client($options['version'], $options['hostname'], $options['port'], $options['uri'], true);
+        // no SSL validation for the CAS server
+        $this->casClient->setNoCasServerValidation();
+
+        return $this->casClient;
+    }
+
+    /**
+     * Spécifie le client CAS.
+     *
+     * @param phpCAS $casClient
+     * @return self
+     */
+    public function setCasClient(phpCAS $casClient)
+    {
+        $this->casClient = $casClient;
+        return $this;
+    }
+
+    /**
+     * @param ModuleOptions $options
+     */
+    public function setOptions(ModuleOptions $options)
+    {
+        $this->options = $options;
+    }
+
+    /**
+     * @return ModuleOptions
+     */
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    /**
+     * get ldap people mapper
+     *
+     * @return LdapPeopleMapper
+     */
+    public function getLdapPeopleMapper()
+    {
+        return $this->ldapPeopleMapper;
+    }
+
+    /**
+     * set ldap people mapper
+     *
+     * @param LdapPeopleMapper $mapper
+     * @return self
+     */
+    public function setLdapPeopleMapper(LdapPeopleMapper $mapper)
+    {
+        $this->ldapPeopleMapper = $mapper;
+
+        return $this;
+    }
+
+    /**
+     * @param RouteInterface $router
+     */
+    public function reconfigureRoutesForCasAuth(RouteInterface $router)
+    {
+        if (! $this->isCasEnabled()) {
+            return;
+        }
+        if (!$router instanceof RouteStackInterface) {
+            return;
+        }
+
+        $router->addRoutes([
+            // remplace les routes existantes (cf. config du module)
+            'zfcuser' => [
+                'type'          => 'Literal',
+                'priority'      => 1000,
+                'options'       => [
+                    'route'    => '/auth',
+                    'defaults' => [
+                        'controller' => 'zfcuser',
+                        'action'     => 'index',
+                    ],
+                ],
+                'may_terminate' => true,
+                'child_routes'  => [
+                    'login'  => [
+                        'type'    => 'Segment',
+                        'options' => [
+                            'route'    => '/connexion[/:type]',
+                            'defaults' => [
+                                'controller' => 'zfcuser',
+                                'action'     => 'authenticate', // zappe l'action 'login'
+                            ],
+                        ],
+                    ],
+                    'logout' => [
+                        'type'    => 'Literal',
+                        'options' => [
+                            'route'    => '/deconnexion',
+                            'defaults' => [
+                                'controller' => 'zfcuser',
+                                'action'     => 'logout',
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/CasServiceFactory.php b/src/UnicaenAuth/Service/CasServiceFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..88f70de8831aea8078494a83036120081adb81f4
--- /dev/null
+++ b/src/UnicaenAuth/Service/CasServiceFactory.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use Interop\Container\ContainerInterface;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenAuth\Options\ModuleOptions;
+
+class CasServiceFactory
+{
+    public function __invoke(ContainerInterface $container)
+    {
+        $service = new CasService();
+
+        /** @var User $userService */
+        $userService = $container->get('unicaen-auth_user_service');
+        $service->setUserService($userService);
+
+        /** @var mixed $router */
+        $router = $container->get('router');
+        $service->setRouter($router);
+
+        $options = array_merge(
+            $container->get('zfcuser_module_options')->toArray(),
+            $container->get('unicaen-auth_module_options')->toArray());
+        $service->setOptions(new ModuleOptions($options));
+
+        /** @var LdapPeopleMapper $ldapPeopleMapper */
+        $ldapPeopleMapper = $container->get('ldap_people_mapper');
+        $service->setLdapPeopleMapper($ldapPeopleMapper);
+
+        return $service;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/ShibService.php b/src/UnicaenAuth/Service/ShibService.php
index eee2d0f6e811d9343ba3d7b3a7a29303dd153160..3c07a8ad8c509fca98c83be9593a12c98c2f40ce 100644
--- a/src/UnicaenAuth/Service/ShibService.php
+++ b/src/UnicaenAuth/Service/ShibService.php
@@ -111,7 +111,9 @@ EOS;
      */
     public function isShibbolethEnabled()
     {
-        return array_key_exists('enable', $this->shibbolethConfig) && (bool) $this->shibbolethConfig['enable'];
+        return
+            array_key_exists('enabled', $this->shibbolethConfig) && (bool) $this->shibbolethConfig['enabled'] ||
+            array_key_exists('enable', $this->shibbolethConfig) && (bool) $this->shibbolethConfig['enable'];
     }
 
     /**
@@ -451,6 +453,10 @@ EOS;
      */
     public function reconfigureRoutesForShibAuth(TreeRouteStack $router)
     {
+        if (! $this->isShibbolethEnabled()) {
+            return;
+        }
+
         $router->addRoutes([
             // remplace les routes existantes (cf. config du module)
             'zfcuser' => [
@@ -466,9 +472,9 @@ EOS;
                 'may_terminate' => true,
                 'child_routes'  => [
                     'login' => [
-                        'type' => 'Literal',
+                        'type' => 'Segment',
                         'options' => [
-                            'route' => '/connexion',
+                            'route' => '/connexion[/:type]',
                             'defaults' => [
                                 'controller' => 'zfcuser', // NB: lorsque l'auth Shibboleth est activée, la page propose
                                 'action'     => 'login',   //     2 possibilités d'auth : LDAP et Shibboleth.
diff --git a/src/UnicaenAuth/Service/ShibServiceFactory.php b/src/UnicaenAuth/Service/ShibServiceFactory.php
index fd16d912664f2e63b2888d1fa9947066c16815e5..cbf84dc888ca32c940e579d824f823740bad6519 100644
--- a/src/UnicaenAuth/Service/ShibServiceFactory.php
+++ b/src/UnicaenAuth/Service/ShibServiceFactory.php
@@ -13,7 +13,7 @@ class ShibServiceFactory
         $moduleOptions = $container->get('unicaen-auth_module_options');
 
         $service = new ShibService();
-        $service->setShibbolethConfig($moduleOptions->getShibboleth());
+        $service->setShibbolethConfig($moduleOptions->getShib());
         $service->setUsurpationAllowedUsernames($moduleOptions->getUsurpationAllowedUsernames());
 
         return $service;
diff --git a/src/UnicaenAuth/Service/Traits/CasServiceAwareTrait.php b/src/UnicaenAuth/Service/Traits/CasServiceAwareTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..9919a7cb80eb183b19ffe2f7ac80dc80673c9eaa
--- /dev/null
+++ b/src/UnicaenAuth/Service/Traits/CasServiceAwareTrait.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace UnicaenAuth\Service\Traits;
+
+use UnicaenAuth\Service\CasService;
+
+trait CasServiceAwareTrait
+{
+    /**
+     * @var CasService
+     */
+    protected $casService;
+
+    /**
+     * @param CasService $casService
+     */
+    public function setCasService(CasService $casService)
+    {
+        $this->casService = $casService;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/User.php b/src/UnicaenAuth/Service/User.php
index 490793c346c3f9ab39130b4aa916c73f28337ff9..4112e74c6cca045ce884d1b877eec96d4c4c276b 100644
--- a/src/UnicaenAuth/Service/User.php
+++ b/src/UnicaenAuth/Service/User.php
@@ -28,7 +28,6 @@ use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions;
 /**
  * Service traitant des utilisateurs locaux de l'application.
  *
- * @see \UnicaenAuth\Authentication\Adapter\AbstractFactory
  * @author Unicaen
  */
 class User implements EventManagerAwareInterface
diff --git a/src/UnicaenAuth/Service/UserContext.php b/src/UnicaenAuth/Service/UserContext.php
index a0a18f0bb14ad340e657eaf98aa11cdba6a91303..65d2db486ef71526148fe9501164157fc20aa56c 100644
--- a/src/UnicaenAuth/Service/UserContext.php
+++ b/src/UnicaenAuth/Service/UserContext.php
@@ -212,6 +212,8 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
     /**
      * Retourne parmi tous les rôles de l'utilisateur courant ceux qui peuvent être sélectionnés.
      *
+     * NB: si plus d'un rôle sont sélectionnables, on zappe le rôle "Authentifié".
+     *
      * @return array
      */
     public function getSelectableIdentityRoles()
@@ -221,6 +223,11 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
         };
         $roles  = array_filter($this->getIdentityRoles(), $filter);
 
+        // si plus d'un rôle sont sélectionnables, on zappe le rôle "Authentifié"
+        if (count($roles) > 1 && isset($roles['user'])) {
+            unset($roles['user']);
+        }
+
         return $roles;
     }
 
@@ -228,38 +235,42 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
      * Si un utilisateur est authentifié, retourne le rôle utilisateur sélectionné,
      * ou alors le premier sélectionnable si aucun n'a été sélectionné.
      *
-     * NB: Si un rôle est spécifié en session comme devant être le prochain rôle sélectionné,
-     * c'est lui qui est pris en compte.
-     *
-     * @return RoleInterface
+     * @return RoleInterface|null
      */
     public function getSelectedIdentityRole()
     {
-        if ($this->getNextSelectedIdentityRole()) {
-            $this->getSessionContainer()->selectedIdentityRole = $this->getNextSelectedIdentityRole();
+        // Si aucun utilisateur n'est authentifié, basta !
+        if (! $this->getIdentity()) {
+            return null;
         }
 
-        if (null === $this->getSessionContainer()->selectedIdentityRole && $this->getIdentity()) {
-            $roles = $this->getSelectableIdentityRoles();
-            $this->setSelectedIdentityRole(reset($roles));
+        // NB: Si un rôle est spécifié en session comme devant être le prochain rôle sélectionné,
+        // c'est lui qui est pris en compte.
+        if ($next = $this->getNextSelectedIdentityRole()) {
+            $this->getSessionContainer()->selectedIdentityRole = $next; // écriture en session
         }
 
-        $roleId = $this->getSessionContainer()->selectedIdentityRole;
-        if ($roleId) {
-//            $roles = $this->getServiceAuthorize()->getRoles(); // Récupération de tous les rôles du provider
-            $roles = $this->getIdentityRoles();
-            if (isset($roles[$roleId])) {
-                $role = $roles[$roleId];
-            } else {
-                $role = null;
-            }
+        // Si en session aucun rôle n'est sélectionné ou si cette sélection n'est pas valide,
+        // on sélectionne le 1er rôle sélectionnable.
+        $selectedRoleId = $this->getSessionContainer()->selectedIdentityRole;
+        $selectableRoles = $this->getSelectableIdentityRoles();
+        if (null === $selectedRoleId || ! isset($selectableRoles[$selectedRoleId])) {
+            $firstSelectableRoleId = reset($selectableRoles);
+            $this->setSelectedIdentityRole($firstSelectableRoleId); // écriture en session
+        }
 
-            if ($this->isRoleValid($role)) {
-                return $role;
-            }
+        // Rôle sélectionné en session.
+        $roleId = $this->getSessionContainer()->selectedIdentityRole; // lecture en session
+        if (! $roleId) {
+            return null;
         }
 
-        return null;
+        $role = $selectableRoles[$roleId];
+        if (! $this->isRoleValid($role)) {
+            return null;
+        }
+
+        return $role;
     }
 
     /**
diff --git a/src/UnicaenAuth/View/Helper/AbstractConnectViewHelper.php b/src/UnicaenAuth/View/Helper/AbstractConnectViewHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..7239d1bd24e1869c0d342b6000dad6c382cf8816
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/AbstractConnectViewHelper.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use Zend\Form\Form;
+use Zend\View\Helper\AbstractHelper;
+use Zend\View\Renderer\PhpRenderer;
+use Zend\View\Resolver\TemplatePathStack;
+
+/**
+ * Aide de vue dessinant un formulaire d'authentification d'un type particulier,
+ * si l'authentification de ce type est activée.
+ *
+ * @method PhpRenderer getView()
+ * @author Unicaen
+ */
+class AbstractConnectViewHelper extends AbstractHelper
+{
+    /**
+     * @var string
+     */
+    protected $type;
+
+    /**
+     * @var string
+     */
+    protected $title;
+
+    /**
+     * @var string
+     */
+    protected $description;
+
+    /**
+     * @var bool
+     */
+    protected $enabled = true;
+
+    /**
+     * @var bool
+     */
+    protected $passwordReset = false;
+
+    /**
+     * @var Form
+     */
+    protected $form;
+
+    /**
+     * @param string $type
+     * @return AbstractConnectViewHelper
+     */
+    public function setType(string $type): AbstractConnectViewHelper
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * @param bool $enabled
+     * @return $this
+     */
+    public function setEnabled($enabled = true)
+    {
+        $this->enabled = $enabled;
+
+        return $this;
+    }
+
+    /**
+     * @param string $title
+     * @return $this
+     */
+    public function setTitle(string $title): AbstractConnectViewHelper
+    {
+        $this->title = $title;
+        return $this;
+    }
+
+    /**
+     * @param string|null $description
+     * @return AbstractConnectViewHelper
+     */
+    public function setDescription(?string $description): AbstractConnectViewHelper
+    {
+        $this->description = $description;
+        return $this;
+    }
+
+    /**
+     * @param bool $passwordReset
+     * @return $this
+     */
+    public function setPasswordReset(bool $passwordReset): AbstractConnectViewHelper
+    {
+        $this->passwordReset = $passwordReset;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    /**
+     * @return string
+     */
+    public function getTitle(): string
+    {
+        return $this->title;
+    }
+
+    /**
+     * @return string
+     */
+    public function getDescription(): ?string
+    {
+        return $this->description;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isEnabled(): bool
+    {
+        return $this->enabled;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPasswordReset(): bool
+    {
+        return $this->passwordReset;
+    }
+
+    /**
+     * @param Form $form
+     * @return $this
+     */
+    public function __invoke(Form $form)
+    {
+        $this->form = $form;
+
+        $this->getView()->resolver()->attach(
+            new TemplatePathStack(['script_paths' => [__DIR__ . "/partial"]])
+        );
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        try {
+            return $this->getView()->render("connect", [
+                'type' => $this->type,
+                'title' => $this->title,
+                'description' => $this->description,
+                'enabled' => $this->enabled,
+                'form' => $this->form,
+                'redirect' => null,
+                'passwordReset' => $this->passwordReset,
+            ]);
+        } catch (\Exception $e) {
+            return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/CasConnectViewHelper.php b/src/UnicaenAuth/View/Helper/CasConnectViewHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..8cc460a280702a5c129c898a8e33dd1a7c9abb7b
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/CasConnectViewHelper.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use Zend\View\Renderer\PhpRenderer;
+
+/**
+ * Aide de vue dessinant le bouton de connexion via Shibboleth,
+ * si l'authentification Shibboleth est activée.
+ *
+ * @method PhpRenderer getView()
+ * @author Unicaen
+ */
+class CasConnectViewHelper extends AbstractConnectViewHelper
+{
+    public function __construct()
+    {
+        $this->setType('cas');
+        $this->setTitle("Authentification centralisée");
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/CasConnectViewHelperFactory.php b/src/UnicaenAuth/View/Helper/CasConnectViewHelperFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..af671df0b853fd976472d7eb2dcde8ab89f36ff0
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/CasConnectViewHelperFactory.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use Interop\Container\ContainerInterface;
+use UnicaenAuth\Options\ModuleOptions;
+
+class CasConnectViewHelperFactory
+{
+    /**
+     * @param ContainerInterface $container
+     * @return CasConnectViewHelper
+     */
+    public function __invoke(ContainerInterface $container)
+    {
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
+        $config = $moduleOptions->getCas();
+
+        $enabled = isset($config['enabled']) && (bool) $config['enabled'];
+        $description = $config['description'] ?? null;
+
+        $helper = new CasConnectViewHelper();
+        $helper->setEnabled($enabled);
+        $helper->setDescription($description);
+
+        return $helper;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/ConnectViewHelper.php b/src/UnicaenAuth/View/Helper/ConnectViewHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..b5eebed04c2e2c18d70150ba8991a13ad051a5cc
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/ConnectViewHelper.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use UnicaenApp\Exception\RuntimeException;
+use Zend\Form\Form;
+use Zend\View\Helper\AbstractHelper;
+use Zend\View\Renderer\PhpRenderer;
+
+/**
+ * Aide de vue dessinant le formulaire correspondant au type d'authentification spécifié.
+ *
+ * @method PhpRenderer getView()
+ * @author Unicaen
+ */
+class ConnectViewHelper extends AbstractHelper
+{
+    /**
+     * @param string $type
+     * @param Form $form
+     * @return AbstractConnectViewHelper
+     */
+    public function __invoke(string $type, Form $form)
+    {
+        switch ($type) {
+            case 'shib':
+                return $this->view->shibConnect($form);
+            case 'cas':
+                return $this->view->casConnect($form);
+            case 'db':
+                return $this->view->dbConnect($form);
+            case 'ldap':
+                return $this->view->ldapConnect($form);
+        }
+
+        throw new RuntimeException("Aucune aide de vue pour le type '$type'");
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/DbConnectViewHelper.php b/src/UnicaenAuth/View/Helper/DbConnectViewHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b0ec684c5cd21666c07ecedeaeeb23796d1988f
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/DbConnectViewHelper.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use Zend\View\Renderer\PhpRenderer;
+
+/**
+ * Aide de vue dessinant le formulaire d'authentification locale,
+ * si l'authentification locale est activée.
+ *
+ * @method PhpRenderer getView()
+ * @author Unicaen
+ */
+class DbConnectViewHelper extends AbstractConnectViewHelper
+{
+    public function __construct()
+    {
+        $this->setType('db');
+        $this->setTitle("Avec un compte local");
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/DbConnectViewHelperFactory.php b/src/UnicaenAuth/View/Helper/DbConnectViewHelperFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb4b8f40d695c8388dde27cad8e9c748edc4f5e2
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/DbConnectViewHelperFactory.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use Interop\Container\ContainerInterface;
+use UnicaenAuth\Options\ModuleOptions;
+
+class DbConnectViewHelperFactory
+{
+    /**
+     * @param ContainerInterface $container
+     * @return DbConnectViewHelper
+     */
+    public function __invoke(ContainerInterface $container)
+    {
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
+        $config = $moduleOptions->getDb();
+
+        $enabled = isset($config['enabled']) && (bool) $config['enabled'];
+        $description = $config['description'] ?? null;
+
+        $helper = new DbConnectViewHelper();
+        $helper->setEnabled($enabled);
+        $helper->setDescription($description);
+        $helper->setPasswordReset(true);
+
+        return $helper;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/LdapConnectViewHelper.php b/src/UnicaenAuth/View/Helper/LdapConnectViewHelper.php
index dbccf4fc2b9dbd0a44bfa80e0cc0069dc8123c40..6cad097126af9eead52ddf8cce74ba7733f400fa 100644
--- a/src/UnicaenAuth/View/Helper/LdapConnectViewHelper.php
+++ b/src/UnicaenAuth/View/Helper/LdapConnectViewHelper.php
@@ -2,75 +2,17 @@
 
 namespace UnicaenAuth\View\Helper;
 
-use Zend\Form\Form;
-use Zend\View\Helper\AbstractHelper;
-use Zend\View\Renderer\PhpRenderer;
-use Zend\View\Resolver\TemplatePathStack;
-
 /**
  * Aide de vue dessinant le formulaire d'authentification LDAP,
  * si l'authentification LDAP est activée.
  *
- * @method PhpRenderer getView()
  * @author Unicaen
  */
-class LdapConnectViewHelper extends AbstractHelper
+class LdapConnectViewHelper extends AbstractConnectViewHelper
 {
-    /**
-     * @var bool
-     */
-    protected $enabled = true;
-
-    /**
-     * @var Form
-     */
-    protected $form;
-
-    /**
-     * @param bool $enabled
-     * @return $this
-     */
-    public function setEnabled($enabled = true)
-    {
-        $this->enabled = $enabled;
-
-        return $this;
-    }
-
-    /**
-     * @param Form $form
-     * @return $this
-     */
-    public function __invoke(Form $form)
+    public function __construct()
     {
-        $this->form = $form;
-
-        $this->getView()->resolver()->attach(
-            new TemplatePathStack(['script_paths' => [__DIR__ . "/partial"]])
-        );
-
-        return $this;
-    }
-
-    /**
-     * @return string
-     */
-    public function __toString()
-    {
-        if (! $this->enabled) {
-            return '';
-        }
-
-        try {
-            return $this->getView()->render("connect", [
-                'title' => null,
-                'enabled' => $this->enabled,
-                'form' => $this->form,
-                'redirect' => null,
-                'passwordReset' => false,
-            ]);
-        } catch (\Exception $e) {
-            return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
-        }
+        $this->setType('ldap');
+        $this->setTitle("Avec mon compte établissement");
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/LdapConnectViewHelperFactory.php b/src/UnicaenAuth/View/Helper/LdapConnectViewHelperFactory.php
index 056875650838c1bf6ec94ce4665e6b9b87c360ec..aaef569227dffff3bc4d10c3b109e4d3dfcfa45d 100644
--- a/src/UnicaenAuth/View/Helper/LdapConnectViewHelperFactory.php
+++ b/src/UnicaenAuth/View/Helper/LdapConnectViewHelperFactory.php
@@ -15,12 +15,14 @@ class LdapConnectViewHelperFactory
     {
         /** @var ModuleOptions $moduleOptions */
         $moduleOptions = $container->get('unicaen-auth_module_options');
-        $ldapArrayConfig = $moduleOptions->getLdap();
+        $config = $moduleOptions->getLdap();
 
-        $ldapEnabled = isset($ldapArrayConfig['enabled']) && (bool) $ldapArrayConfig['enabled'];
+        $enabled = isset($config['enabled']) && (bool) $config['enabled'];
+        $description = $config['description'] ?? null;
 
         $helper = new LdapConnectViewHelper();
-        $helper->setEnabled($ldapEnabled);
+        $helper->setEnabled($enabled);
+        $helper->setDescription($description);
 
         return $helper;
     }
diff --git a/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php b/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php
index b2475ed8867a5b22a1ebeec08e66631f330fb5a9..189a8037322edf3c106f87674b8f07509c547d47 100644
--- a/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php
+++ b/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php
@@ -2,10 +2,7 @@
 
 namespace UnicaenAuth\View\Helper;
 
-use Zend\Form\Form;
-use Zend\View\Helper\AbstractHelper;
 use Zend\View\Renderer\PhpRenderer;
-use Zend\View\Resolver\TemplatePathStack;
 
 /**
  * Aide de vue dessinant le formulaire d'authentification locale,
@@ -13,64 +10,9 @@ use Zend\View\Resolver\TemplatePathStack;
  *
  * @method PhpRenderer getView()
  * @author Unicaen
+ * @deprecated Remplacé par DbConnectViewHelper
  */
-class LocalConnectViewHelper extends AbstractHelper
+class LocalConnectViewHelper extends DbConnectViewHelper
 {
-    /**
-     * @var bool
-     */
-    protected $enabled = true;
 
-    /**
-     * @var Form
-     */
-    protected $form;
-
-    /**
-     * @param bool $enabled
-     * @return $this
-     */
-    public function setEnabled($enabled = true)
-    {
-        $this->enabled = $enabled;
-
-        return $this;
-    }
-
-    /**
-     * @param Form $form
-     * @return $this
-     */
-    public function __invoke(Form $form)
-    {
-        $this->form = $form;
-
-        $this->getView()->resolver()->attach(
-            new TemplatePathStack(['script_paths' => [__DIR__ . "/partial"]])
-        );
-
-        return $this;
-    }
-
-    /**
-     * @return string
-     */
-    public function __toString()
-    {
-        if (! $this->enabled) {
-            return '';
-        }
-
-        try {
-            return $this->getView()->render("connect", [
-                'title' => "Avec un compte local",
-                'enabled' => $this->enabled,
-                'form' => $this->form,
-                'redirect' => null,
-                'passwordReset' => true,
-            ]);
-        } catch (\Exception $e) {
-            return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/LocalConnectViewHelperFactory.php b/src/UnicaenAuth/View/Helper/LocalConnectViewHelperFactory.php
index 79a43f683b202d5e8d2a1457d5ec86f932629001..4524addc157d3e05df0a7e644554b1488156bc0c 100644
--- a/src/UnicaenAuth/View/Helper/LocalConnectViewHelperFactory.php
+++ b/src/UnicaenAuth/View/Helper/LocalConnectViewHelperFactory.php
@@ -5,6 +5,11 @@ namespace UnicaenAuth\View\Helper;
 use Interop\Container\ContainerInterface;
 use UnicaenAuth\Options\ModuleOptions;
 
+/**
+ * Class LocalConnectViewHelperFactory
+ *
+ * @deprecated
+ */
 class LocalConnectViewHelperFactory
 {
     /**
@@ -15,12 +20,15 @@ class LocalConnectViewHelperFactory
     {
         /** @var ModuleOptions $moduleOptions */
         $moduleOptions = $container->get('unicaen-auth_module_options');
-        $config = $moduleOptions->getLocal();
+        $config = $moduleOptions->getDb();
 
         $enabled = isset($config['enabled']) && (bool) $config['enabled'];
+        $description = $config['description'] ?? null;
 
         $helper = new LocalConnectViewHelper();
         $helper->setEnabled($enabled);
+        $helper->setDescription($description);
+        $helper->setPasswordReset(true);
 
         return $helper;
     }
diff --git a/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
index 44669aa08087f9a6bac65b3a3dd5cb2c719fb572..0130a573b4d86cd7911c77204be052a0ecd4e4b6 100644
--- a/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
+++ b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
@@ -2,8 +2,6 @@
 
 namespace UnicaenAuth\View\Helper;
 
-use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
-use Zend\View\Helper\AbstractHelper;
 use Zend\View\Renderer\PhpRenderer;
 
 /**
@@ -13,36 +11,11 @@ use Zend\View\Renderer\PhpRenderer;
  * @method PhpRenderer getView()
  * @author Unicaen
  */
-class ShibConnectViewHelper extends AbstractHelper
+class ShibConnectViewHelper extends AbstractConnectViewHelper
 {
-    use ShibServiceAwareTrait;
-
-    /**
-     * @return string
-     */
-    public function __toString()
-    {
-        try {
-            return $this->render();
-        } catch (\Exception $e) {
-            return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
-        }
-    }
-
-    /**
-     * @return string
-     */
-    private function render()
+    public function __construct()
     {
-        if (! $this->shibService->isShibbolethEnabled()) {
-            return '';
-        }
-
-        $shibUrl = $this->getView()->url('auth/shibboleth', [], ['query' => $this->getView()->queryParams()], true);
-
-        return <<<EOS
-<h3 class="connect-title">Via la fédération d'identité</h3> 
-<a href="$shibUrl" class="btn btn-success btn-lg">Fédération d'identité Renater</a>
-EOS;
+        $this->setType('shib');
+        $this->setTitle("Via la fédération d'identité");
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php b/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
index 1d207a2c377becc90f6397ff8dc9bc57397eb09a..60311ca98dfd0b9a6f807f7ab7421ef85e763bc0 100644
--- a/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
+++ b/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
@@ -3,7 +3,7 @@
 namespace UnicaenAuth\View\Helper;
 
 use Interop\Container\ContainerInterface;
-use UnicaenAuth\Service\ShibService;
+use UnicaenAuth\Options\ModuleOptions;
 
 class ShibConnectViewHelperFactory
 {
@@ -13,11 +13,16 @@ class ShibConnectViewHelperFactory
      */
     public function __invoke(ContainerInterface $container)
     {
-        /** @var ShibService $shibService */
-        $shibService = $container->get(ShibService::class);
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $container->get('unicaen-auth_module_options');
+        $config = $moduleOptions->getShib();
+
+        $enabled = isset($config['enabled']) && (bool) $config['enabled'];
+        $description = $config['description'] ?? null;
 
         $helper = new ShibConnectViewHelper();
-        $helper->setShibService($shibService);
+        $helper->setEnabled($enabled);
+        $helper->setDescription($description);
 
         return $helper;
     }
diff --git a/src/UnicaenAuth/View/Helper/UserCurrent.php b/src/UnicaenAuth/View/Helper/UserCurrent.php
index 3a1d51da005f46b9e5b657dbb50009142cc3dddc..f5271a00e91714178bb87ecefae32dd8f05ef99e 100644
--- a/src/UnicaenAuth/View/Helper/UserCurrent.php
+++ b/src/UnicaenAuth/View/Helper/UserCurrent.php
@@ -44,8 +44,12 @@ class UserCurrent extends UserAbstract
         
         if ($this->getIdentity()) {
             if ($userProfileSelectable) {
-		        // DS : cas où aucun rôle n'est sélectionné, on affiche le rôle "user"
-		        $role = $this->getUserContext()->getSelectedIdentityRole() ?: $this->getUserContext()->getIdentityRole('user');
+		        $role = $this->getUserContext()->getSelectedIdentityRole();
+                // cas où aucun rôle n'est sélectionné : on affiche le 1er rôle sélectionnable ou sinon "user"
+		        if ($role === null) {
+		            $selectableRoles = $this->getUserContext()->getSelectableIdentityRoles();
+                    $role = current($selectableRoles) ?: $this->getUserContext()->getIdentityRole('user');
+                }
                 $status .= sprintf(", <small class='role-libelle'>%s</small>", !method_exists($role, '__toString') ? $role->getRoleId() : $role);
             }
         
diff --git a/src/UnicaenAuth/View/Helper/partial/connect.phtml b/src/UnicaenAuth/View/Helper/partial/connect.phtml
index 7d758b6ec3ffe9e5ca9c8119599b8742c1b3c0b2..34aed63b49f389da9a7d450c755dad4d8265cceb 100644
--- a/src/UnicaenAuth/View/Helper/partial/connect.phtml
+++ b/src/UnicaenAuth/View/Helper/partial/connect.phtml
@@ -1,11 +1,15 @@
 <?php
 
+use Application\View\Renderer\PhpRenderer;
 use Zend\Form\Form;
 
 /**
+ * @var PhpRenderer $this
  * @var bool   $enabled
  * @var Form   $form
+ * @var string $type
  * @var string $title
+ * @var string $description
  * @var string $redirect
  * @var bool   $passwordReset
  */
@@ -16,35 +20,28 @@ use Zend\Form\Form;
         <?php echo $title ?>
     </h3>
 <?php endif ?>
-
-<?php echo $this->form()->openTag($form) ?>
-
-<?php if (($errors = $this->formErrors($form))): ?>
-    <p><?php echo $errors ?></p>
-<?php endif ?>
-<p class="connect-identity">
-    <?php
-    $identity = $form->get($name = 'identity')->setAttributes(['id' => $name, 'class' => 'form-control']);
-    echo $this->formLabel($identity);
-    echo $this->formInput($identity);
-    ?>
-</p>
-<p class="connect-credentials">
-    <?php
-    $identity = $form->get($name = 'credential')->setAttributes(['id' => $name, 'class' => 'form-control']);
-    echo $this->formLabel($identity);
-    echo $this->formInput($identity);
-    ?>
-    <?php if ($passwordReset): ?>
-        <a class="connect-credentials-lost" href="<?php echo $this->url('auth/requestPasswordReset') ?>">Mot de passe oublié</a>
-    <?php endif ?>
-</p>
-<?php if ($redirect): ?>
-    <input type="hidden" name="redirect" value="<?php echo $redirect ?>"/>
+<?php if ($description): ?>
+    <div class="connect-description">
+        <?php echo $description ?>
+    </div>
 <?php endif ?>
 
-<p class="connect-submit">
-    <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?>
-</p>
+<?php if ($messages = $this->flashMessenger('zfcuser-login-form')): ?>
+<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 endif ?>
 
-<?php echo $this->form()->closeTag() ?>
+<?php //if ($type === 'shib'): ?>
+<!--    --><?php //$shibUrl = $this->url('auth/shibboleth', [], ['query' => $this->queryParams()], true); ?>
+<!--    <a href="--><?php //echo $shibUrl ?><!--" class="btn btn-success btn-lg">Fédération d'identité Renater</a>-->
+<?php //elseif ($type === 'cas'): ?>
+<!--    --><?php //$casUrl = $this->url('auth/cas', [], ['query' => $this->queryParams()], true); ?>
+<!--    <a href="--><?php //echo $casUrl ?><!--" class="btn btn-success btn-lg">Authentification centralisée</a>-->
+<?php //else: ?>
+    <?php echo $this->render('form.phtml', compact('form', 'type', 'redirect', 'passwordReset')) ?>
+<?php //endif ?>
diff --git a/src/UnicaenAuth/View/Helper/partial/form.phtml b/src/UnicaenAuth/View/Helper/partial/form.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..2932e52e1a6fc0573e8ed76091f5f98bccb7c974
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/partial/form.phtml
@@ -0,0 +1,53 @@
+<?php
+
+use Application\View\Renderer\PhpRenderer;
+use Zend\Form\Form;
+
+/**
+ * @var PhpRenderer $this
+ * @var Form $form
+ * @var string $type
+ * @var string $redirect
+ * @var bool $passwordReset
+ */
+?>
+
+<?php echo $this->form()->openTag($form) ?>
+
+<?php if (($errors = $this->formErrors($form))): ?>
+    <p><?php echo $errors ?></p>
+<?php endif ?>
+
+<?php if ($form->has($name = 'identity')): ?>
+<p class="connect-identity">
+    <?php
+    $identity = $form->get($name = 'identity')->setAttributes(['id' => $name, 'class' => 'form-control']);
+    echo $this->formLabel($identity);
+    echo $this->formInput($identity);
+    ?>
+</p>
+<?php endif ?>
+<?php if ($form->has($name = 'credential')): ?>
+<p class="connect-credentials">
+    <?php
+    $identity = $form->get($name = 'credential')->setAttributes(['id' => $name, 'class' => 'form-control']);
+    echo $this->formLabel($identity);
+    echo $this->formInput($identity);
+    ?>
+    <?php if ($passwordReset): ?>
+        <a class="connect-credentials-lost" href="<?php echo $this->url('auth/requestPasswordReset') ?>">Mot de passe oublié</a>
+    <?php endif ?>
+</p>
+<?php endif ?>
+
+<input type="hidden" name="type" value="<?php echo $type ?>"/>
+
+<?php if ($redirect): ?>
+    <input type="hidden" name="redirect" value="<?php echo $redirect ?>"/>
+<?php endif ?>
+
+<p class="connect-submit">
+    <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?>
+</p>
+
+<?php echo $this->form()->closeTag() ?>
diff --git a/tests/config/autoload/unicaen-auth.local.php b/tests/config/autoload/unicaen-auth.local.php
index 9d2948186a2ea65a24d3f7f9a931a6a62be42d9d..3ef83992db8e40fc898f2895255a2653d053940c 100644
--- a/tests/config/autoload/unicaen-auth.local.php
+++ b/tests/config/autoload/unicaen-auth.local.php
@@ -7,11 +7,10 @@
  */
 $settings = [
     /**
-     * Paramètres de connexion au serveur CAS :
-     * - pour désactiver l'authentification CAS, le tableau 'cas' doit être vide.
-     * - pour l'activer, renseigner les paramètres.
+     * Paramètres de connexion au serveur CAS.
      */
     'cas' => [
+//        'enabled' => true,
 //        'connection' => array(
 //            'default' => array(
 //                'params' => array(
diff --git a/view/unicaen-auth/auth/login-tabs.phtml b/view/unicaen-auth/auth/login-tabs.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..ac4587f664ccc9af7c5d8970a071c40c31572c1f
--- /dev/null
+++ b/view/unicaen-auth/auth/login-tabs.phtml
@@ -0,0 +1,69 @@
+<?php
+
+use UnicaenAuth\Authentication\Adapter;
+use UnicaenAuth\View\Helper\AbstractConnectViewHelper;
+use Zend\Form\Form;
+
+/**
+ * Génération des différents types de formulaire de connexion activés.
+ *
+ * @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 string $redirect URL demandée nécessitant authentification
+ *
+ * @method AbstractConnectViewHelper connect()
+ */
+
+/** @var Form $form */
+$form->prepare();
+$form->setAttributes([
+    'class' => 'form-horizontal',
+    'role' => 'form',
+]);
+
+/** @var AbstractConnectViewHelper[] $helpers */
+$helpers = [];
+foreach ($types as $t => $config) {
+    if (isset($config['type'])) {
+        if ($config['type'] === 'local') {
+            $substitut = $config['type'];
+            $helpers[$substitut] = $this->connect($t, $form);
+            $helpers[$substitut]->setTitle("Avec un compte local");
+        }
+    } else {
+        $helpers[$t] = $this->connect($t, $form);
+    }
+}
+if ($type === null || ! array_key_exists($type, $helpers)) {
+    $type = key($helpers);
+}
+$activeHelper = null;
+?>
+
+<ul class="nav nav-tabs nav-justified">
+    <?php foreach ($helpers as $key => $helper): ?>
+        <?php
+        if ($key === $type) {
+            $activeHelper = $helper;
+            $activeClass = 'active';
+        } else {
+            $activeClass = '';
+        }
+        ?>
+        <li role="presentation" class="<?php echo $activeClass ?>">
+            <?php $query = $redirect ? ['redirect' => $redirect] : [] ?>
+            <a href="<?php echo $this->url('zfcuser/login', ['type' => $key], ['query' => $query]) ?>"><?php echo $helper->getTitle() ?></a>
+        </li>
+    <?php endforeach ?>
+</ul>
+
+<div class="tab-content">
+    <div role="tabpanel" class="tab-pane active">
+        <?php
+        $title = $activeHelper->getTitle();
+        $activeHelper->setTitle(""); // pour éviter que le title soit répété
+        ?>
+        <?php echo (string)$activeHelper ?>
+    </div>
+</div>
diff --git a/view/unicaen-auth/auth/login.phtml b/view/unicaen-auth/auth/login.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..e2624af1faf55750d61a12f8a9dbf3875f066ae6
--- /dev/null
+++ b/view/unicaen-auth/auth/login.phtml
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @var PhpRenderer $this
+ * @var array[] $types Types d'authentification activés, ex: ['db' => ['enabled'=>true, 'type'=>'local'], 'shib' => ['enabled'=>true]]
+ * @var string $type Type d'authentification courante : 'db', 'ldap', 'shib'
+ * @var Form $loginForm Formulaire de connexion
+ * @var string $redirect URL demandée nécessitant authentification
+ */
+
+use Application\View\Renderer\PhpRenderer;
+use Zend\Form\Form;
+
+$this->headTitle("Connexion") ?>
+
+<div class="div-connexion">
+    <h1 class="page-header"><?php echo $this->translate("Connexion"); ?></h1>
+    <?php echo $this->partial('unicaen-auth/auth/login-tabs', [
+        'types' => $types,
+        'type' => $type,
+        'form' => $loginForm,
+        'redirect' => $redirect,
+    ]); ?>
+</div>
+
+
+<!-- Création d'un compte local (si autorisée) -->
+<?php if ($this->enableRegistration) : ?>
+<div id="div-connexion" class="panel panel-primary">
+    <?php echo $this->translate("Not registered?"); ?> <a href="<?php echo $this->url('zfcuser/register') . ($this->redirect ? '?redirect=' . $this->redirect : '') ?>"><?php echo $this->translate("Sign up!"); ?></a>
+</div>
+<?php endif; ?>
+
+
+<script type="text/javascript">
+    $(function() {
+        // focus sur le 1er champ vide visible
+        var $input = $("input").filter(function() {
+            return $(this).is(":visible") && this.value === "";
+        });
+        if ($input.length) $input.get(0).focus();
+    });
+</script>
\ No newline at end of file
diff --git a/view/unicaen-auth/auth/shibboleth.phtml b/view/unicaen-auth/auth/shibboleth.phtml
index 0543055a61199e8a8cd8035821acf0b45bda2b50..0b466983884bf40d0d95a1552cbd73a55d95e0d3 100644
--- a/view/unicaen-auth/auth/shibboleth.phtml
+++ b/view/unicaen-auth/auth/shibboleth.phtml
@@ -11,7 +11,7 @@
 <pre>
     'unicaen-auth' => [
         ...
-        'shibboleth' => [
+        'shib' => [
             'enable' => true,
         ],
     ],
@@ -30,7 +30,7 @@
 <pre>
     'unicaen-auth' => [
         ...
-        'shibboleth' => [
+        'shib' => [
             'enable' => true,
             'simulate' => [
                 'eppn'           => $eppn = 'premierf@univ.fr',
diff --git a/view/zfc-user/user/login.phtml b/view/zfc-user/user/login.phtml
deleted file mode 100644
index 1f6e9a7bbb698210f3d695a242f4880c393e42ea..0000000000000000000000000000000000000000
--- a/view/zfc-user/user/login.phtml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-/**
- * @var PhpRenderer $this
- *
- * @method LocalConnectViewHelper localConnect()
- * @method LdapConnectViewHelper ldapConnect()
- * @method ShibConnectViewHelper shibConnect()
- */
-
-use UnicaenAuth\View\Helper\LdapConnectViewHelper;
-use UnicaenAuth\View\Helper\LocalConnectViewHelper;
-use UnicaenAuth\View\Helper\ShibConnectViewHelper;
-use Zend\Form\Form;
-use Zend\View\Renderer\PhpRenderer;
-
-$this->headTitle("Connexion") ?>
-
-<?php
-/** @var Form $form */
-$form = $this->loginForm;
-$form->prepare();
-$form->setAttributes([
-    'action' => $this->url('zfcuser/login'),
-    'method' => 'post',
-    'class'  => 'form-horizontal',
-    'role'   => 'form']);
-?>
-
-<style>
-    .div-connexion { max-width: 350px; margin: auto auto 30px; }
-    .div-connexion h2 { margin: 0; }
-    .div-connexion .btn-primary { margin: 10px 0; }
-    .div-connexion .btn-success { margin: 10px 0; display: inline-block; }
-</style>
-
-<div class="div-connexion panel panel-primary">
-
-    <div class="panel-heading">
-        <h2><?php echo $this->translate("Connexion"); ?></h2>
-    </div>
-
-    <div class="panel-body">
-        <?php
-        $localAuthHtml = (string) $this->localConnect($form);
-        $ldapAuthHtml  = (string) $this->ldapConnect($form);
-        $shibAuthHtml  = (string) $this->shibConnect($form);
-        echo implode('<hr>', array_filter([$ldapAuthHtml, $shibAuthHtml, $localAuthHtml]));
-        ?>
-    </div>
-</div>
-
-
-<!-- Création d'un compte local (si autorisée) -->
-<?php if ($this->enableRegistration) : ?>
-<div id="div-connexion" class="panel panel-primary">
-    <?php echo $this->translate("Not registered?"); ?> <a href="<?php echo $this->url('zfcuser/register') . ($this->redirect ? '?redirect=' . $this->redirect : '') ?>"><?php echo $this->translate("Sign up!"); ?></a>
-</div>
-<?php endif; ?>
-
-
-<script type="text/javascript">
-    // focus sur le 1er champ vide
-    $("input").filter(function() {
-        return this.value === "";
-    }).get(0).focus();
-</script>
\ No newline at end of file