I. Le fonctionnement du HTTP

HTTP est un protocole sans état. Il n'inclut pas de notion d'identité de base et chaque requête est donc indépendante.

Cela signifie que le serveur ne me reconnaît pas entre chaque requête.

C'est la base du fonctionnement du Web, et l'opposé d'une connexion client - serveur tel que FTP.

L'avantage est que le serveur n'a pas à gérer ses clients, tout le monde est logé à la même enseigne. L'inconvénient est que l'ajout d'un mécanisme d'identification va ajouter un surplus d'information à chaque requête.

II. Les sessions, ou comment transformer HTTP en protocole à état

Le mécanisme de sessions prend place à la fois côté client et serveur. Il est activé de base sur une application Rails par le biais d'un middleware (ActionDispatch::Session::CookieStore) qui peut être désactivé au besoin.

Lorsqu'un nouvel utilisateur arrive sur l'application, si le serveur ne reçoit pas d'identifiant de session il va en créer un qu'il va transmettre dans sa réponse.

Côté client cet identifiant va être stocké dans un cookie. Les cookies ne sont pas forcément des fichiers plats. Sous Chrome les cookies sont stockés dans une base SQLite et sont limités à 4 ko en contenu.

Il est fortement déconseillé de placer des objets en session, car cela pourrait poser des soucis d'encodage/décodage.

Par exemple on mettra un id utilisateur plutôt que l'objet utilisateur. Qui plus est, cela permet de limiter l'espace occupé par le cookie.

III. Les sessions avec Ruby on Rails

Ruby on Rails possède plusieurs mécanismes de stockage de session. Par défaut il utilise le cookie store qui stocke tout sur le client et ne nécessite aucune configuration.

Dans ce cas l'identifiant de session n'a pas d'intérêt pour la partie serveur.

Ce paramètre peut être modifié dans config/initializers/session_store.rb.

 
Sélectionnez

App::Application.config.session_store :cookie_store, :key => '_app_application_session'

Les autres mécanismes de stockage sont la base de données et le cache. Dans ces cas l'identifiant de session va permettre au serveur de retrouver les données associées en base ou en cache.

L'avantage de ces mécanismes est de pouvoir stocker plus de données voire d'améliorer les performances dans le cas du cache.

Attention toutefois, si votre serveur de cache vient à tomber vous n'avez plus d'utilisateurs connectés. D'autre part ces mécanismes impliquent une purge côté applicatif si vous ne voulez pas stocker des milliers de sessions fantômes.

Dernier point, vous aurez besoin de partager ces sessions entre serveurs si vous mettez en place du load balancing sur votre application.

IV. Le contenu des cookies

Le contenu des cookies est encodé avec le secret de l'application Rails, qui peut être surchargé dans votre initializer avec l'option secret_token.

Voici un exemple de contenu de cookie :

 
Sélectionnez

BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWNhZTFlMjY0YWRlZTc2NjNhZDc4YzY4YzkwMzk3NWVlBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMXBEZlQrdkVXZndjdGpQd1JSNTRQUjhYWlp6WHFldUlZYURZZklkYmh4UVE9BjsARkkiGXdhcmRlbi51c2VyLnVzZXIua2V5BjsAVFsISSIJVXNlcgY7AEZbBmkE8NcBLUkiIiQyYSQxMCRMUXNTdU9rbk4wSVIySUZORHFESVcuBjsAVA==--f58cf55b4d11b47c398103643b0ffe7ffdbff309

Le cookie présente deux parties, séparées par -, d'un côté le contenu et de l'autre côté la signature. Séparons-les :

 
Sélectionnez

  content = "BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWNhZTFlMjY0YWRlZTc2NjNhZDc4YzY4YzkwMzk3NWVlBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMXBEZlQrdkVXZndjdGpQd1JSNTRQUjhYWlp6WHFldUlZYURZZklkYmh4UVE9BjsARkkiGXdhcmRlbi51c2VyLnVzZXIua2V5BjsAVFsISSIJVXNlcgY7AEZbBmkE8NcBLUkiIiQyYSQxMCRMUXNTdU9rbk4wSVIySUZORHFESVcuBjsAVA=="
  signature = "f58cf55b4d11b47c398103643b0ffe7ffdbff309"

À la réception du cookie, Rails va vérifier que le contenu et la signature correspondent. Pour cela il va utiliser son secret token.

Vous ne devez jamais divulguer ce token où vos sessions seront usurpables.

Voyons quel est le secret de notre application de démo :

 
Sélectionnez

secret = Rails.application.config.secret_token
"0b22c3a50d0ba81e396b880268d8d13c5980896a3761a24bc87e704f3589f9d0ef2528ef7480101e0edeccc5320f6310d54fd06ad7df574ee6e8e349ba01ace2"

Voilà ce que fait Rails pour générer la signature :

 
Sélectionnez

OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, str)
"f58cf55b4d11b47c398103643b0ffe7ffdbff309"

Effectivement, on retombe bien sur notre signature.

Par le biais de ce mécanisme, modifier le contenu du cookie ne permet pas d'usurper une session, car la signature ne sera plus valable et notre framework s'en rendra compte.

Attention, si les cookies sont signés leur contenu n'est toutefois pas encrypté.

Il est tout à fait possible d'extraire le contenu d'un cookie.

 
Sélectionnez

session = Marshal.load(Base64.decode64(CGI.unescape(content)))
{
              "session_id" => "cae1e264adee7663ad78c68c903975ee",
             "_csrf_token" => "pDfT+vEWfwctjPwRR54PR8XZZzXqeuIYaDYfIdbhxQQ=",
    "warden.user.user.key" => [
        [0] "User",
        [1] [
            [0] 755095536
        ],
        [2] "$2a$10$LQsSuOknN0IR2IFNDqDIW."
    ]
}

J'espère que cet article vous aura permis de mieux appréhender le fonctionnement des sessions, notamment dans Ruby on Rails.

V. Conclusion et remerciements

Cet article est publié avec l'aimable autorisation de Synbioz.

L'article original peut être lu sur le blog de Synbioz : Le mécanisme de sessions en rails.

Nous tenons à remercier f-leb et ClaudeLELOUP pour leur relecture attentive de cet article.