Sam & Max » header http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 La protection CSRF de Django et les requêtes Ajax 19 http://sametmax.com/la-protection-csrf-de-django-et-les-requetes-ajax/ http://sametmax.com/la-protection-csrf-de-django-et-les-requetes-ajax/#comments Tue, 10 Jun 2014 04:18:36 +0000 http://sametmax.com/?p=10438 attaques CSRF est dans le top 10 des erreurs les plus chiantes en Django, main dans la main avec les fichiers statiques qui ne marchent pas, les URL qui ne matchent pas et les CBV qui nheuuuu, juste pas.]]> La protection contre les attaques CSRF est dans le top 10 des erreurs les plus chiantes en Django, main dans la main avec les fichiers statiques qui ne marchent pas, les URL qui ne matchent pas et les CBV qui nheuuuu, juste pas.

Une fois qu’on a compris le principe, ça va pour la prog normal, mais un jour on a besoin de faire un POST en Ajax, et on se retrouve avec une erreur invisible. Après avoir dégainé Firebug, on comprend qu’on a une 403 forbidden, et votre cerveau finit (la durée galérienne est plus ou moins longue selon les profiles, les heures de sommeil et les phases de la lune) par réaliser qu’on n’a pas envoyé le token CSRF. Merde.

C’est là que généralement les gens sortent du @csrf_exempt, ou carrément, en finissent avec cette solution radicale :

MIDDLEWARE_CLASSES = (
    'django.middleware.gzip.GZipMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
)

Mais c’est quand même dommage d’en arriver là alors qu’on peut le faire proprement.

D’abord, le token est sauvegardé dans un cookie. Il faut donc le récupérer.

// ue petite fonction pour récupérer la valeur d'un cookie,
// puisque bien entendu, comme toutes les APIS javascripts,
// les choses qu'on fait le plus souvent ne sont pas incluses
// en natif. Oui je suis aigri.
function getCookie(name) {
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                return decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
}

Sinon, pour les fainéants, il y a y a un plugin jquery qui permet de faire $.cookie('nom').

Ensuite, on attache cette valeur en header avec toutes les requêtes POST. Comme ça, plus besoin de l’inclure manuellement, on peut faire ses requêtes sans y penser.

Avec jQuery :

$.ajaxSetup({
    // fonction appelée avant d'envoyer une requête AJAX
    beforeSend: function(xhr, settings) {
         // on ajoute le header que si la requête est pour le site en cours
         // (URL relative) et est de type POST
         if (!/^https?:.*/.test(settings.url)  && settings.type == "POST") {
             // attachement du token dans le header
             xhr.setRequestHeader("X-CSRFToken",  getCookie('csrftoken'));
         }
     }
});

Avec AngularJs :

// interception de la configuration du provider HTTP
// qui possède un mécanisme déjà tout prêt pour ça
votre_app.config(function($httpProvider) {
    $httpProvider.defaults.xsrfCookieName = 'csrftoken';
    $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
});

Attention, si l’app n’est pas servie par Django (template statique avec uniquement des appels Ajax), il faut faire au moins un GET avant de faire son premier POST afin d’obtenir le cookie.

]]>
http://sametmax.com/la-protection-csrf-de-django-et-les-requetes-ajax/feed/ 19