Application CORS et Reverse Proxy

Lors d'un de mes projets de développement, j'ai eu besoin de rendre accessible les requêtes web service OData d'un serveur Microsoft Dynamics NAV depuis une web ressource de Microsoft Dynamics 365 Online, et éventuellement localement. Pour ce faire, et éviter d'avoir des problèmes de requêtes CORS (Cross Origin Resource Sharing) j'ai décidé de mettre en place un reverse proxy.

Cet article a pour but de mettre en lumière les étapes pour la mise en place, et les problèmes/solutions rencontrées.

Mise en place d'un Reverse Proxy sur IIS pour Dynamics NAV

Afin de mettre en place un reverse proxy en place, il faudra tout d'abord installer URL Rewrite 2.0 via l'application Web Platfom Installer. Une fois le module IIS installé, sélectionner le site sur lequel mettre en place le Reverse Proxy et utiliser le bouton URL Rewrite.

Description de l’image

Une fois ouvert, sélectionner Add Rule(s)... dans le panneau de droite. Puis sélectionner Reverse Proxy.

Description de l’image

Si toutefois, le module ADD n'était pas installé, il suffira de suivre les étapes qui ne devraient prendre qu'une minute. Si c'est le premier Reverse Proxy mis en place sur le site, une fenêtre demandera une confirmation de sécurité.

Description de l’image

Inscrire le nom du serveur NAV dans le champs Inbound Rules, puis sélectionner ou nom le champ Enable SSL Offloading si vous souhaitez que les requêtes HTTPS soient transférées en HTTP.

Définir ensuite les règles de routage selon un modèle, dans cet exemple : ws/(.*). Ainsi toutes les requêtes envoyé après le préfixe ws seront rerouté vers l'URL définie au bas : http://azure-nav:7058/BasicWS/OData/Company('<Nom société>')/{R:1}.

Description de l’image

Le reverse Proxy est prêt à rerouter vers URL en interne vers une adresse "complexe".

Ajax et le preflight CORS

Lorsqu'on demande à Ajax de faire une requêtes CORS (Cross Origin Resource Sharing), celui-ci effectue une requête de type OPTIONS pour valider les réponses du serveur sur les domaines autorisées, etc. Si comme moi, vous souhaitez héberger votre application sur plusieurs domaines, vous serez alors tenté de mettre en réponse : Access-Control-Allow-Origin : * Access-Control-Allow-Credentials : true

Le problème, c'est que ces deux paramètres sont incompatibles. Si vous avez besoin de passer des identifiants, typiquement pour vous authentifier dans l'application après le reverse proxy, il faut alors finter un peu les réponses faites par le reverse proxy pour qu'il répond 200 - OK dans le cas d'un option et modifié les réponses faites en fonction du type de requête.

Voici le fichier web.config à la base de mon site :

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <outboundRules>
                <rule name="Set Access-Control-Allow-Methods for OPTIONS response" preCondition="OPTIONS" patternSyntax="Wildcard">
                    <match serverVariable="RESPONSE_Access-Control-Allow-Methods" pattern="*" negate="false" />
                    <action type="Rewrite" value="GET" />
                </rule>
                <rule name="Set Access-Control-Allow-Headers for OPTIONS response" preCondition="OPTIONS" patternSyntax="Wildcard">
                    <match serverVariable="RESPONSE_Access-Control-Allow-Headers" pattern="*" negate="false" />
                    <action type="Rewrite" value="Origin, X-Requested-With, Content-Name, Content-Type, Accept, Authorization" />
                </rule>
                <rule name="Set Access-Control-Allow-Origin for OPTIONS response" preCondition="OPTIONS" patternSyntax="Wildcard">
                    <match serverVariable="RESPONSE_Access-Control-Allow-Origin" pattern="*" negate="false" />
                    <action type="Rewrite" value="<DOMAINE CIBLE>" />
                </rule>
                <rule name="Set Access-Control-Max-Age for OPTIONS response" preCondition="OPTIONS" patternSyntax="Wildcard">
                    <match serverVariable="RESPONSE_Access-Control-Max-Age" pattern="*" negate="false" />
                    <action type="Rewrite" value="3600" />
                </rule>
                <rule name="Set X-Content-Type-Options for OPTIONS response" preCondition="OPTIONS" patternSyntax="Wildcard">
                    <match serverVariable="RESPONSE_X-Content-Type-Options" pattern="*" negate="false" />
                    <action type="Rewrite" value="nosniff" />
                </rule>
                <rule name="Set Access-Control-Allow-Credentials for OPTIONS response" preCondition="OPTIONS" patternSyntax="Wildcard">
                    <match serverVariable="RESPONSE_Access-Control-Allow-Credentials" pattern="*" negate="false" />
                    <action type="Rewrite" value="true" />
                </rule>
                <preConditions>
                    <preCondition name="OPTIONS">
                        <add input="{REQUEST_METHOD}" pattern="OPTIONS" />
                    </preCondition>
                </preConditions>
            </outboundRules>
            <rules>
                <rule name="BasicAuthRewrite" stopProcessing="true">
                    <match url="ws/(.*)" />
                    <action type="Rewrite" url="http://<SERVEUR NAV>:7058/BasicWS/OData/Company('<Nom société>')/{R:1}" appendQueryString="true" logRewrittenUrl="true" />
                    <conditions logicalGrouping="MatchAny">
                        <add input="{REQUEST_METHOD}" pattern="OPTIONS" negate="true" />
                    </conditions>   
                </rule>
                <rule name="OPTIONS" patternSyntax="Wildcard" stopProcessing="true">
                    <match url="ws/(.*)" />
                    <conditions logicalGrouping="MatchAny">
                        <add input="{REQUEST_METHOD}" pattern="OPTIONS" />
                    </conditions>
                    <action type="CustomResponse" statusCode="200" subStatusCode="0" statusReason="OK" statusDescription="OK" />
                </rule>
            </rules>
        </rewrite>
        <httpProtocol>
            <customHeaders>
                <add name="Access-Control-Allow-Origin" value="*" />
                <add name="Access-Control-Allow-Credentials" value="true" />
            </customHeaders>
        </httpProtocol>
    </system.webServer>
</configuration>