Ajax et XSS : autoriser les requêtes grâce au CORS

Lorsqu’on développe une application RESTful exploitant des webservices disponibles sur un autre domaine, on est rapidement confronté à des questions de sécurité, notamment le cross-site scripting (XSS).

Le problème

Le cas typique dans lequel on rencontre cette problématique est celui d’un site mobile « m.domaine.com » qui récupère des données sur le site desktop « domaine.com ». Toutes les requêtes Ajax partant de « m.domaine.com » vers « domaine.com » seront bloquées par la règle de sécurité Same Origin Policy.

La solution

La solution à cette problématique est le mécanisme du Cross-origin resource sharing (CORS) spécifié par le W3C et utilisable dans Apache et dans la plupart des navigateurs modernes.

La configuration Apache

Le vhost du domaine cible des requêtes (« domaine.com » dans notre exemple) doit spécifier des directives « Access-Control-Allow » :

<VirtualHost *:80> 
    ServerName domaine.com

    Header set Access-Control-Allow-Origin "http://m.domaine.com"
    Header set Access-Control-Allow-Credentials true
    Header set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
    Header set Access-Control-Allow-Headers "content-type, accept, set-cookie"
</VirtualHost>

Astuce

Grâce à la directive « Access-Control-Allow-Headers », on peut indiquer quels entêtes HTTP seront transmis. En autorisant « set-cookie », on peut récupérer, sur « m.domaine.com », le cookie d’une session ouverte sur « domaine.com ». Ainsi, on réalise facilement un petit SSO maison !

Les requêtes Ajax

Avec jQuery, la requête Ajax devra contenir le flag withCredentials à true dans les paramètres xhrFields (voir également la doc de XMLHttpRequest pour les autres implémentations).

$.ajax({
    url: 'http://domaine.com',
    xhrFields: {
        withCredentials: true
    }
});

Attention à la subtilité !

On trouve très souvent la syntaxe ci-dessous :

Header set Access-Control-Allow-Origin "*"

Cette directive ne fonctionne pas lorsqu’on passe le flag withCredentials à true dans les paramètres xhrFields de la requête Ajax. Dans ce cas, il faut préciser explicitement le domaine autorisé :

Header set Access-Control-Allow-Origin "http://m.domaine.com"

RequireJS et ParsleyJS : utiliser les shims pour traduire vos contrôles de formulaires

ParsleyJS est une librairie Javascript permettant de contrôler la saisie de ses formulaires. Elle est livrée avec de nombreuses traductions de ses messages d’erreurs. Si vous gérez les dépendances de votre projet avec RequireJS, voici comment charger élégamment les traductions de Parsley.

Configuration des dépendances avec RequireJS

On indique à RequireJS le path vers les modules « parsley », « parsleyfr » et « parsleyfrextra ». Puis, on utilise les shims pour définir 2 nouvelles dépendances du module « parsley » :

  • parsleyfr
  • parsleyfrextra
require.config({
    paths: {
        "jquery":           "libs/jquery/dist/jquery",
        "underscore":       "libs/underscore/underscore",
        "backbone":         "libs/backbone/backbone",
        "parsley":          "libs/parsleyjs/dist/parsley",
        "parsleyfr":        "libs/parsleyjs/src/i18n/fr",
        "parsleyfrextra":   "libs/parsleyjs/src/i18n/fr.extra",
    },
    shim: {
        "parsley": {
            deps: ["parsleyfr", "parsleyfrextra"]
        },
    }
});

Déclaration d’une dépendance à Parsley

Ensuite, dans nos modules qui dépendent de Parsley, on indique normalement la dépendance dans la fonction « define ».

define([
        "backbone",
        "underscore",
        "parsley"
    ],
    function(Backbone, _) {

    }
);

Grâce aux shims, RequireJS va charger les modules « parsleyfr » et « parsleyfrextra » avant le module « parsley ». Vous pourrez ensuite changer la locale de vos contrôles de formulaires en appelant la méthode « setLocale » de ParsleyJS.

window.Parsley.setLocale('fr');

Un peu de lecture

BackboneJS : écouter le router au chargement de l’application

Dans une application BackboneJS, si vous avez besoin d’écouter l’événement route déclenché par votre router, pensez à procéder dans le bon ordre :

// 1 - Instanciation du Router
App.router = new Router();

// 2 - Instanciation des écouteurs
App.menu = new MenuView(); // écoute l'événement "route" du router

// 3 - Démarrage de la navigation
App.history = Backbone.history.start();

Le router ne démarre pas au moment de son instanciation. En réalité, c’est l’historique qui démarre le router ( Backbone.history.start ).
Donc si vous lancez l’historique avant d’instancier vos écouteurs, vous n’écouterez pas l’événement route déclenché au chargement de votre application, mais seulement l’événement route déclenché aux changements de route, lors de la navigation des utilisateurs.

Limites Google Maps : géolocalisation et gécodage

On emploie à tort le terme « géolocalisation », à ne pas confondre avec la notion de « géocodage ».

Géolocalisation

La « géolocalisation » est un procédé qui permet d’obtenir les coordonnées GPS d’un terminal (smartphone par exemple) à un instant donné, grâce à un système de positionnement par satellite.

Géocodage

Le « géocodage » est un procédé qui permet d’obtenir des coordonnées GPS à partir d’une adresse postale (recherchée dans une base de données par exemple). Le géocodage n’implique pas l’utilisation de satellites.

Limitations de l’API Google Maps

La géolocalisation et le géocodage sont deux opérations bien différenciées dans l’API Google Maps. Au 02/03/2014, Google communique les limitations ci-dessous pour la géolocalisation et le géocodage :

  • 100 requêtes de géolocalisation par jour
  • 2500 requêtes de géocodage par jour

Sources