Blog tech

Le mécanisme de session en rails

Rédigé par Martin Catty | 4 octobre 2012

Le fonctionnement d’HTTP

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

Cela signifie que le serveur ne me reconnait 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.

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

Le mécanisme de session 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 à 4ko 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.

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’id session n’a pas d’intérêt pour la partie serveur.

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

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’id 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 voir d’améliorer les performances dans le cas du cache.

Attention toutefois, si votre serveur de cache vient à tomber vous n’avez plus d’utilisateur connectés. D’autre part ces mécanismes impliquent une purge côté applicatif si vous ne voulez pas stocker des miliers 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.

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:

BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJWNhZTFlMjY0YWRlZTc2NjNhZDc4YzY4YzkwMzk3NWVlBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMXBEZlQrdkVXZndjdGpQd1JSNTRQUjhYWlp6WHFldUlZYURZZklkYmh4UVE9BjsARkkiGXdhcmRlbi51c2VyLnVzZXIua2V5BjsAVFsISSIJVXNlcgY7AEZbBmkE8NcBLUkiIiQyYSQxMCRMUXNTdU9rbk4wSVIySUZORHFESVcuBjsAVA==--f58cf55b4d11b47c398103643b0ffe7ffdbff309

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

  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. Note: vous ne devez jamais divulguer ce token où vos sessions seront usurpables.

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

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

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

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.

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 permi de mieux appréhender le fonctionnement des sessions, notamment dans Ruby on Rails.

L’équipe Synbioz.

Libres d’être ensemble.