Initiation aux fichiers .htaccess

Présentation

Avant de manipuler un fichier .htaccess, il faut d'abord situer son rôle dans le fonctionnement d'Apache.

Le serveur HTTP Apache

Apache HTTP Server, souvent abrégé en Apache, est un logiciel de serveur web. Il reçoit des requêtes HTTP envoyées par un navigateur, puis renvoie en réponse une page, un fichier, une image, un script ou un code d'erreur.

Apache repose sur un système de modules. On peut ainsi lui ajouter ou lui retirer certaines fonctionnalités selon les besoins du site.

  • redirections HTTP
  • réécriture d'URL
  • gestion des accès
  • pages d'erreur personnalisées
  • compression et mise en cache

Tous les sites n'ont pas besoin des mêmes fonctions. Apache permet justement d'adapter sa configuration à chaque projet.

Configuration principale, VirtualHost et fichier .htaccess

La configuration d'Apache se fait normalement dans les fichiers principaux du serveur. C'est à ce niveau que l'on définit les réglages généraux du serveur ainsi que, souvent dans des blocs VirtualHost, le dossier racine d'un site, les noms de domaine associés ou encore certains comportements propres à ce site.

Un VirtualHost permet à Apache de gérer plusieurs sites sur une même machine, chacun avec son propre nom de domaine et son propre dossier racine.

Sur un hébergement mutualisé, le développeur n'a généralement pas accès à cette configuration principale. Apache permet alors d'utiliser des fichiers locaux nommés .htaccess.

Un fichier .htaccess contient des directives qui s'appliquent à un dossier donné et à ses sous-dossiers. Cela permet de modifier localement certains comportements du serveur, sans devoir éditer la configuration globale.

Un fichier .htaccess ne fonctionne que si le serveur autorise son usage. Cette possibilité dépend notamment de la directive AllowOverride, définie dans la configuration principale d'Apache, souvent dans un fichier comme httpd.conf, apache2.conf ou dans un fichier inclus, par exemple un fichier de VirtualHost.

Certaines fonctionnalités exigent aussi l'activation d'un module. Par exemple, les règles de réécriture reposent sur mod_rewrite.

Un avantage pratique du fichier .htaccess est qu'il est relu à chaque requête. On peut donc modifier ses règles sans redémarrer Apache. En contrepartie, ce mécanisme a un coût et reste moins performant qu'une configuration placée directement dans les fichiers principaux du serveur.

Comment tester les exemples de ce chapitre

Pour tester les exemples de ce chapitre, il suffit de créer un petit projet PHP servi par Apache en local avec un outil comme Laragon, Wamp, XAMPP ou MAMP.

Dans la plupart des cas, un fichier .htaccess devra être placé à la racine du projet. Si un exemple nécessite un autre emplacement, cela sera précisé.

Il est possible d'écrire des commentaires dans un fichier .htaccess. Toute ligne qui commence par # est ignorée par Apache.


                    # Ceci est un commentaire
                    RewriteEngine On
                

Si une erreur 500 Internal Server Error apparaît, cela signale très souvent une faute de syntaxe, une directive non autorisée ou un module manquant.

Enfin, attention au cache du navigateur. Certaines redirections permanentes 301 peuvent être mémorisées. En cas de doute, testez dans une fenêtre privée ou videz le cache du navigateur.

Redirection externe avec Redirect

La directive Redirect permet de demander au navigateur d'aller vers une autre adresse.

Il s'agit d'une redirection externe. Cela signifie que le serveur ne renvoie pas directement le contenu final. Il indique d'abord au navigateur qu'il doit faire une nouvelle requête vers une autre URL.


                    Redirect /ancienne-page.php /nouvelle-page.php
                
  1. Le navigateur demande /ancienne-page.php au serveur.
  2. Apache répond au navigateur qu'il doit réaliser une redirection vers /nouvelle-page.php et accompagne cette résponse d'un code de redirection.
  3. Le navigateur envoie une nouvelle requête vers /nouvelle-page.php.
  4. Apache renvoie finalement la ressource demandée (/nouvelle-page.php).

Avec Redirect, si aucun code n'est précisé, Apache utilise une redirection temporaire 302.

Notez que les chemins comparés dans la directive commencent par un slash car il s'agit d'URI relatives à la racine du site.

Redirection temporaire


                    Redirect temp /index.php /maintenance.php
                

Cette écriture est équivalente à une redirection 302. Elle convient lorsqu'un changement d'adresse n'est que provisoire.


                    Redirect 302 /index.php /maintenance.php
                

Redirection permanente


                    Redirect permanent /ancienne-page.php /nouvelle-page.php
                

Cette écriture est équivalente à une redirection 301. Elle indique qu'une page a changé d'adresse de manière durable.


                    Redirect 301 /ancienne-page.php /nouvelle-page.php
                

Impact sur le référencement

Une redirection 301 indique aux moteurs de recherche que la nouvelle URL doit devenir la référence. Une redirection 302 indique au contraire que le changement est temporaire.

On peut voir cela comme un transfert de "crédit de référencement", même si ce terme est une simplification. En pratique, une URL peut accumuler au fil du temps différents signaux, par exemple des liens venant d'autres sites, des partages ou une présence dans l'index d'un moteur de recherche. Lorsqu'on remplace durablement une ancienne URL par une nouvelle, une redirection 301 aide les moteurs de recherche à comprendre que ces signaux doivent désormais être associés à la nouvelle adresse.

Cette façon de faire est utile pour trois raisons :

  • Elle aide à faire apparaître la bonne URL dans les résultats de recherche,
  • elle évite de disperser les signaux entre plusieurs adresses pour une même page,
  • elle permet aux visiteurs qui utilisent encore l'ancienne URL d'arriver automatiquement au bon endroit.

Lorsqu'une URL change de manière durable, il faut en général conserver l'ancienne URL et la rediriger vers la nouvelle avec une redirection 301. En pratique, on garde souvent cette redirection au moins un an, et parfois beaucoup plus longtemps si l'ancienne adresse a été partagée, indexée par des moteurs de recherche ou enregistrée dans des favoris.

Des outils comme Google Search Console peuvent aider à vérifier si Google connaît encore l'ancienne URL et la voit bien comme une URL redirigée. Pour savoir si cette ancienne adresse est encore réellement utilisée par des visiteurs, il faut plutôt consulter les journaux du serveur ou un outil d'analyse d'audience.

Gestion des erreurs HTTP

Lorsqu'une requête provoque une erreur, Apache renvoie un code HTTP adapté, par exemple 404 pour une ressource introuvable ou 403 pour un accès refusé.

La directive ErrorDocument permet d'associer un contenu personnalisé à une erreur.


                    ErrorDocument 404 /ma-page-404-perso.php
                

Quand la destination d'ErrorDocument est un chemin local commençant par un slash, Apache effectue un traitement interne. Le navigateur ne fait pas de nouvelle requête vers une autre URL. L'URL visible reste celle de départ, tandis qu'Apache renvoie le contenu de la page personnalisée avec le bon code d'erreur.

  1. Le navigateur demande une URL inexistante.
  2. Apache détecte une erreur 404.
  3. Apache charge en interne la page définie dans ErrorDocument.
  4. Le navigateur reçoit le contenu de cette page personnalisée accompagné du code HTTP 404.
  5. L'URL visible reste celle de départ.

Codes d'erreur fréquents

  • 401 authentification requise
  • 403 accès interdit
  • 404 ressource introuvable
  • 500 erreur interne du serveur

Recommandations

Une page d'erreur personnalisée doit rester claire, simple et ne pas révéler d'informations sensibles sur la configuration du serveur.

En général, il n'est pas souhaitable que l'utilisateur accède directement à ces pages personnalisées comme s'il s'agissait de pages normales du site. Plus loin dans les exercices, nous verrons comment bloquer cet accès direct.

Mini-rappels sur les motifs utilisés dans ce chapitre

Les règles de réécriture utilisent souvent des motifs proches des expressions régulières. Voici quelques symboles que fréquents que nous utiliserons dans ce chapitre.

  • ^ indique le début de la chaîne
  • $ indique la fin de la chaîne
  • \. représente un point réel
  • \s représente un espace
  • ? rend un élément optionnel
  • + signifie un ou plusieurs
  • * signifie zéro, un ou plusieurs
  • [ ... ] décrit un ensemble de caractères possibles
  • [^ ... ] signifie n'importe quel caractère sauf ceux listés

Premiers pas avec RewriteRule

La directive RewriteRule est plus flexible que Redirect. Elle peut servir soit à déclencher une redirection externe, soit à réécrire une URL en interne.

Dans ce chapitre, nous utiliserons les termes suivants.

  • Redirection externe quand le navigateur reçoit l'ordre d'aller vers une autre URL et que l'adresse visible change.
  • Réécriture interne quand Apache sert une autre ressource sans modifier l'URL visible.

Avant toute règle de réécriture, il faut activer le moteur de réécriture.


                    RewriteEngine On
                

Redirection externe avec RewriteRule

Pour déclencher une redirection externe avec RewriteRule, on utilise le drapeau [R]. On peut lui associer un code HTTP, par exemple [R=301] pour une redirection permanente ou [R=302] pour une redirection temporaire. Si aucun code n'est précisé et que l'on écrit seulement [R], Apache utilise par défaut le code 302.


                    RewriteEngine On

                    RewriteRule ^ancienne-page\.php$ /nouvelle-page.php [R=301,L]
                    RewriteRule ^contact\.php$ /maintenance.php [R=302,L]
                

Le drapeau [L] est propre à mod_rewrite. Il s'utilise à la fin d'une RewriteRule pour indiquer que, si cette règle correspond, les règles de réécriture suivantes ne doivent pas être évaluées dans le traitement en cours.

Même lorsqu'aucune autre règle de réécriture ne suit pour l'instant, on rencontre souvent ce drapeau par habitude de clarté et de sécurité. Il permet d'indiquer explicitement que la règle doit s'arrêter là si elle correspond, et il évite des surprises si d'autres règles sont ajoutées plus tard.

Réécriture interne

Si l'on n'utilise pas le drapeau [R], une règle RewriteRule sert très souvent à faire une réécriture interne.


                    RewriteEngine On

                    RewriteRule ^contact$ contact.php [L]
                

Si l'utilisateur demande /contact, Apache sert en interne le fichier contact.php. L'URL affichée reste /contact.

Structure d'une RewriteRule

Une règle RewriteRule possède deux parties :

  • À gauche, un motif.
  • À droite, la destination vers laquelle Apache doit réécrire ou rediriger.

La partie de gauche : le motif

Contrairement aux redirections réalisées avec Redirect, la partie gauche d'une RewriteRule n'est pas comparée à l'URL complète depuis la racine du site, mais au chemin relatif au dossier courant. Il ne faut donc pas écrire de slash initial.

Un motif comme ^contact$ correspond donc à l'URL /contact.

Notez que ce comportement ne vaut que dans le contexte d'un fichier .htaccess. Dans la configuration principale d'Apache ou dans un bloc VirtualHost, la comparaison se fait au contraire sur le chemin de l'URL demandé, donc sur une valeur comme /contact.

La partie de droite : la destination

La partie droite d'une RewriteRule indique la destination à utiliser si la règle correspond. En contexte .htaccess, une destination sans slash initial, comme contact.php, est interprétée relativement au contexte courant (répertoire dans lequel se trouve le fichier .htaccess). Une destination commençant par un slash, comme /contact.php, désigne un chemin d'URL à partir de la racine du site.

Par exemple,
RewriteRule ^contact$ contact.php [L] réécrit en interne vers une destination relative au contexte courant, tandis que
RewriteRule ^contact$ /contact.php [L] réécrit en interne vers une destination exprimée à partir de la racine du site.

Conditionner les règles avec RewriteCond

Une directive RewriteCond permet d'ajouter une condition à la règle qui suit. Autrement dit, la RewriteRule ne s'applique que si le test demandé par la condition est vrai.

Lorsqu'on place plusieurs RewriteCond les unes sous les autres, elles doivent toutes être vraies pour que la règle suivante s'applique. On peut donc les lire comme un ET logique, sauf cas particuliers que nous ne verrons pas ici.

Exemple simple avec HTTPS

Prenons un cas très fréquent. On souhaite rediriger les visiteurs vers la version sécurisée du site, mais uniquement s'ils utilisent encore HTTP.


                    RewriteEngine On

                    # %{HTTPS} vaut off si la requête arrive encore en HTTP
                    RewriteCond %{HTTPS} off

                    # ^ = appliquer la règle à n'importe quelle URL
                    # %{HTTP_HOST} = nom de domaine demandé
                    # %{REQUEST_URI} = chemin demandé
                    # On reconstruit donc la même adresse, mais en HTTPS,
                    # puis on envoie une redirection permanente 301 au navigateur
                    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
                

Cette écriture signifie si la requête n'utilise pas encore HTTPS, alors rediriger le navigateur vers la même adresse en HTTPS.

Dans cette règle, la partie gauche de RewriteRule contient seulement ^. Ce symbole représente le début de la chaîne.

Écrit seul, il correspond donc à n'importe quel chemin, puisque toute URL possède un début. Autrement dit, cette règle est prévue pour pouvoir viser toutes les requêtes.

Ici, le véritable filtre n'est pas la partie gauche de RewriteRule, mais la condition RewriteCond %{HTTPS} off. C'est elle qui décide si la redirection doit réellement s'appliquer.

On pourrait écrire un motif plus long comme .* ou ^.*$, mais cela n'apporterait rien de plus ici. Le symbole ^ suffit pour dire appliquer cette règle à toute requête.

Les écritures de la forme %{...} permettent de lire des informations connues par Apache au sujet de la requête en cours.

  • %{HTTPS} indique si la requête utilise déjà HTTPS ou non.
  • %{HTTP_HOST} correspond au nom de domaine demandé, par exemple mon-site.test.
  • %{REQUEST_URI} correspond au chemin de l'URI en cours de traitement, par exemple /contact.

La destination https://%{HTTP_HOST}%{REQUEST_URI} reconstruit donc la même adresse que celle demandée, mais en version sécurisée.

La condition RewriteCond %{HTTPS} off est utile car elle évite d'appliquer la redirection à une requête qui utilise déjà HTTPS. Sans cette condition, Apache renverrait en boucle une redirection 301 vers la même adresse, jusqu'à ce que le navigateur finisse par signaler qu'il y a trop de redirections.

Dans cette règle, le drapeau [R=301] demande une redirection externe permanente, tandis que [L] indique à mod_rewrite qu'il ne faut pas continuer à évaluer les règles de réécriture suivantes dans le traitement en cours si cette règle correspond.

Capturer une partie du chemin demandé

Jusqu'ici, nous avons surtout travaillé avec des règles qui reconnaissent des chemins fixes, comme /contact ou /inscription.

Mais dans un site réel, certaines URL contiennent une partie variable. Par exemple, un article peut être demandé avec une URL comme /article/42, /article/103 ou /article/250.

Dans ce cas, la règle ne doit pas seulement vérifier si le chemin correspond. Elle doit aussi récupérer la valeur variable, ici le numéro de l'article, pour pouvoir la transmettre au script PHP.

Pour cela, on place une partie du motif entre parenthèses. Les parenthèses créent un groupe capturé. Cela signifie que la partie reconnue par ce morceau du motif est non seulement testée, mais aussi mémorisée pour pouvoir être réutilisée dans la destination.


                    RewriteRule ^article/([0-9]+)$ article.php?id=$1 [L]
                

Dans cet exemple, ^article/([0-9]+)$ correspond à une URL comme /article/42.

La partie ([0-9]+) est un groupe capturé. Elle reconnaît une suite d'un ou plusieurs chiffres et capture la valeur trouvée. Si l'URL demandée vaut /article/42, ce groupe capture 42.

Cette valeur capturée peut ensuite être réutilisée dans la destination avec $1. Le chiffre 1 désigne le premier groupe capturé du motif, c'est-à-dire le premier groupe écrit entre parenthèses.

Ici, il n'y a qu'un seul groupe capturé, donc $1 représente la valeur capturée par ([0-9]+), soit 42. La réécriture devient alors article.php?id=42.

S'il y avait un deuxième groupe capturé dans le motif, on pourrait le réutiliser avec $2, puis $3 pour un troisième, et ainsi de suite.

À partir de là, une seule règle peut gérer de nombreuses URL du même type, tant que leur structure reste identique.

Créer des URL propres pour des pages PHP classiques

Imaginons un petit site composé de fichiers comme contact.php, about.php ou index.php. On souhaite que l'utilisateur voie des URL plus propres, sans l'extension .php.

Une URL propre est une adresse plus lisible et plus claire pour l'utilisateur, par exemple /contact au lieu de /contact.php. Elle montre moins de détails techniques, tout en restant plus agréable à lire, à partager et à mémoriser. Une adresse simple et cohérente peut aussi donner une impression plus claire et plus rassurante au visiteur.

Faire fonctionner l'URL propre en interne

Pour qu'une URL comme /contact fonctionne réellement, Apache doit pouvoir la faire correspondre au fichier contact.php.

Il faut toutefois éviter que cette règle réécrive des ressources dont le navigateur a besoin telles que les images, les feuilles de style, les scripts JavaScript, les vidéos ou encore les dossiers réellement présents sur le site (par exemple si la requête vise un dossier dans lequel Apache doit chercher un fichier d'index). Pour vérifier cela, on utilise la variable %{REQUEST_FILENAME}.

Cette variable représente le chemin réel correspondant à la requête en cours lorsque cette correspondance a déjà été déterminée par Apache. On l'utilise souvent avec -f et -d pour savoir si la requête vise déjà un vrai fichier ou un vrai dossier.


                    RewriteEngine On

                    # Si la requête ne vise ni un fichier réel ni un dossier réel,
                    # on réécrit en interne vers le fichier .php correspondant.
                    RewriteCond %{REQUEST_FILENAME} !-f
                    RewriteCond %{REQUEST_FILENAME} !-d

                    # ^([^\.]+)$ = du début à la fin, accepter un ou plusieurs caractères
                    # tant qu'il n'y a pas de point. Cela permet de viser une URL propre
                    # comme /contact et d'éviter une URL contenant déjà une extension comme /contact.php
                    RewriteRule ^([^\.]+)$ $1.php [L]
                

Cette configuration signifie si la requête ne correspond ni à un vrai fichier ni à un vrai dossier, alors Apache peut réécrire l'URL vers un fichier PHP du même nom.

Ainsi, une visite sur /contact peut être servie par contact.php, alors qu'une vraie image comme /images/logo.png ou un vrai dossier continue à être traité normalement.

Rediriger les anciennes URL en .php vers les URL propres

Si le site existait déjà avec des URL en .php, il est préférable d'ajouter une redirection permanente vers les nouvelles URL propres.

Cela permet d'indiquer au navigateur et aux moteurs de recherche que l'adresse de référence a changé. L'ancienne URL reste utilisable, mais elle renvoie vers la nouvelle.

Pour détecter ce que le navigateur a réellement demandé au départ, on utilise la variable %{THE_REQUEST}.

Il faut bien distinguer %{THE_REQUEST} et %{REQUEST_URI}. %{THE_REQUEST} contient la requête HTTP brute envoyée par le navigateur, par exemple GET /contact.php HTTP/1.1, tandis que %{REQUEST_URI} représente le chemin de l'URI sur lequel Apache est en train de travailler, par exemple /contact.

Cette différence devient importante lorsqu'une réécriture interne a déjà eu lieu. Par exemple, si le navigateur demande /contact et qu'Apache le réécrit en interne vers contact.php ou vers /public/contact, on veut parfois savoir ce que le navigateur a demandé au départ, et parfois savoir sur quel chemin Apache travaille maintenant. C'est pour cela que %{REQUEST_URI} ne doit pas être utilisé dans tous les contextes. Quand on veut détecter une demande directe du navigateur, on préfère %{THE_REQUEST}. Quand on veut tester le chemin actuellement traité par Apache, on utilise plutôt %{REQUEST_URI}.


                    RewriteEngine On

                    # Si le navigateur demande explicitement un fichier .php,
                    # on le redirige vers son URL propre.
                    # %{THE_REQUEST} contient la requête brute envoyée par le navigateur
                    # \s/+         = un espace puis un ou plusieurs /
                    # (.+?)        = capture le chemin demandé, le plus court possible
                    # \.php        = suivi de l'extension .php
                    # (?:[\s?]|$)  = puis un espace, un ? ou la fin de la chaîne
                    # [NC]         = No case (insensbile à la casse)
                    RewriteCond %{THE_REQUEST} \s/+(.+?)\.php(?:[\s?]|$) [NC]
                    RewriteRule ^(.+)\.php$ /$1 [R=301,L]

                    RewriteCond %{REQUEST_FILENAME} !-f
                    RewriteCond %{REQUEST_FILENAME} !-d

                    RewriteRule ^([^\.]+)$ $1.php [L]
                

L'ordre de ces règles est important. Apache les évalue de haut en bas. Il faut donc commencer par la redirection des anciennes URL en .php, car elle doit intercepter les demandes explicites du navigateur. La réécriture interne vers les fichiers .php vient ensuite, car elle sert seulement à faire fonctionner les URL propres.

Ici, %{THE_REQUEST} joue un rôle important. Il permet de détecter seulement les demandes directes faites par le navigateur sur des URL en .php.

Sans ce test, la redirection risquerait aussi de se déclencher après la réécriture interne de /contact vers contact.php. Le navigateur serait alors redirigé en boucle entre les mêmes adresses, jusqu'à finir par signaler qu'il y a trop de redirections.

Éviter le doublon entre / et /index.php

Quand l'utilisateur demande l'URL /, Apache cherche souvent un fichier d'index dans le dossier visé, par exemple index.php. La page d'accueil peut donc être accessible à la fois avec / et avec /index.php.

Il vaut mieux choisir / comme URL de référence, car elle est plus courte, plus naturelle pour l'utilisateur et suffit déjà à désigner la page d'accueil. Cela évite aussi d'avoir deux adresses différentes pour un même contenu.


                    RewriteEngine On

                    # Si le navigateur demande explicitement index ou index.php,
                    # on redirige vers le dossier courant pour ne garder qu'une seule URL pour l'accueil.
                    RewriteCond %{THE_REQUEST} \s/+(.*/)?index(?:\.php)?(?:[\s?]|$) [NC]
                    RewriteRule ^(.*/)?index(?:\.php)?$ /$1 [R=301,L]

                    # Si le navigateur demande explicitement une URL en .php,
                    # on réalise une redirection permanente vers le nom du fichier sans son extension.
                    RewriteCond %{THE_REQUEST} \s/+(.+?)\.php(?:[\s?]|$) [NC]
                    RewriteRule ^(.+)\.php$ /$1 [R=301,L]

                    # Pour les autres URL propres (sans .php),
                    # qui ne sont ni des fichiers ni des dossiers existants,
                    # on réécrit en interne vers le fichier .php correspondant.
                    RewriteCond %{REQUEST_FILENAME} !-f
                    RewriteCond %{REQUEST_FILENAME} !-d
                    RewriteRule ^([^\.]+)$ $1.php [L]
                

L'ordre de ces règles est important. Apache les lit de haut en bas. Il faut d'abord traiter le cas particulier de index et index.php, car ces deux adresses doivent mener vers le dossier courant. Ensuite seulement, on peut traiter les autres fichiers .php et les rediriger vers leur version sans extension.

Ici, si le navigateur demande explicitement /index ou /index.php, Apache réalise une redirection permanente vers /. De la même manière, /admin/index ou /admin/index.php sont redirigés vers /admin/. Cela permet de conserver une seule URL visible pour chaque page d'accueil de dossier.

Selon la configuration du serveur, lorsqu'une URL correspond à un dossier, Apache peut déjà chercher automatiquement un fichier d'accueil dans ce dossier. Il peut par exemple essayer index.html ou index.php, avec un ordre de priorité défini par la configuration. Si votre serveur ne fonctionne pas comme prévu, vous pouvez préciser cet ordre avec la directive DirectoryIndex.


                    DirectoryIndex index.php index.html
                

Avec cette directive, Apache cherche d'abord index.php, puis index.html si le premier fichier n'existe pas. Ainsi, une adresse comme / peut afficher le fichier index.php sans que l'adresse visible devienne /index.php.

Cas d'un site neuf

Sur un site entièrement neuf, les anciennes URL en .php n'ont jamais été publiées. Il n'est donc pas nécessaire de rediriger /contact.php vers /contact.

Dans ce cas, on peut adopter une règle plus stricte. Si le navigateur demande explicitement une URL en .php, Apache renvoie une erreur 404. Si une page d'erreur personnalisée est définie, c'est elle qui sera affichée.


                    RewriteEngine On

                    # Si le navigateur demande explicitement index,
                    # on renvoie une erreur 404.
                    RewriteCond %{ENV:REDIRECT_STATUS} ^$
                    RewriteCond %{THE_REQUEST} \s/+(.*/)?index(?:[\s?]|$) [NC]
                    RewriteRule ^(.*/)?index$ - [R=404,L]

                    # Si le navigateur demande explicitement une URL en .php,
                    # on renvoie une erreur 404.
                    RewriteCond %{ENV:REDIRECT_STATUS} ^$
                    RewriteCond %{THE_REQUEST} \s/+[^\s?]+\.php(?:[\s?]|$) [NC]
                    RewriteRule ^ - [R=404,L]

                    # Pour les autres URL propres,
                    # on réécrit en interne vers le fichier .php correspondant.
                    RewriteCond %{REQUEST_FILENAME} !-f
                    RewriteCond %{REQUEST_FILENAME} !-d
                    RewriteRule ^([^\.]+)$ $1.php [L]

                    # Page d'erreur personnalisée pour les URL refusées
                    ErrorDocument 404 /ma-page-404-perso.php
                

Dans cet exemple, /index, /index.php et les URL demandées explicitement avec l'extension .php sont refusées avec une erreur 404. En revanche, une URL propre comme /contact peut toujours être réécrite en interne vers contact.php.

Le tiret - signifie qu'Apache ne doit pas construire de nouvelle destination. Il conserve le chemin en cours, puis applique simplement le drapeau [R=404]. Ce drapeau demande à Apache de renvoyer une erreur 404. La directive ErrorDocument 404 permet ensuite d'associer une page personnalisée à cette erreur.

Les conditions basées sur REDIRECT_STATUS évitent que ces règles bloquent la page d'erreur personnalisée lorsque Apache l'appelle lui-même. Lorsqu'une page d'erreur locale est utilisée, Apache ajoute notamment la variable REDIRECT_STATUS au contexte de la requête d'erreur.

Lire et créer des variables d'environnement avec mod_rewrite

Jusqu'ici, nous avons surtout utilisé des variables propres à mod_rewrite, comme %{HTTPS}, %{HTTP_HOST} ou %{REQUEST_URI}. Elles permettent à Apache de lire différentes informations sur la requête en cours.

Il existe aussi un autre type de variables les variables d'environnement. Ce sont des informations associées à la requête en cours, que le serveur peut créer lui-même ou que des règles de réécriture peuvent ajouter.

Ces variables permettent à Apache de tester une information stockée dans l'environnement de la requête en cours. Cette information peut avoir été créée automatiquement par Apache, ou ajoutée par une règle de réécriture.

Créer et lire une variable d'environnement avec [E=...]

Une règle de réécriture peut aussi créer elle-même une variable d'environnement. Pour cela, on utilise le drapeau [E=nom:valeur].


                    RewriteEngine On

                    # Création d'une variable d'environnement nommée MA_VARIABLE
                    # ayant pour valeur la chaîne "oui".
                    RewriteRule ^ - [E=MA_VARIABLE:oui, L]

                    # Vérifier si la variable d'environnement MA_VARIABLE
                    # contient bien la chaîne "oui".
                    # Si c'est le cas, la réécriture peut être réalisée.
                    RewriteCond %{ENV:MA_VARIABLE} ^oui$
                    RewriteRule ^test$ test.php [L]
                

Ici, le drapeau [E=MA_VARIABLE:oui] crée une variable d'environnement nommée MA_VARIABLE avec la valeur oui. Une condition placée plus loin peut ensuite la relire avec %{ENV:MA_VARIABLE}.

Dans la pratique, ce mécanisme permet souvent de mémoriser un état temporaire, de marquer une requête ou de distinguer deux situations qui se ressemblent visuellement mais qui n'ont pas la même origine.

Réutiliser une capture dans une variable d'environnement

La valeur stockée dans une variable d'environnement peut aussi venir d'un groupe capturé.


                    RewriteEngine On

                    RewriteCond %{THE_REQUEST} (.+)
                    RewriteRule ^ - [E=MA_VARIABLE:%1, L]
                

Dans cet exemple, %1 réutilise le premier groupe capturé par la RewriteCond. Les pourcentages %n servent donc à récupérer les captures faites dans une condition. Les dollars $n servent, eux, à récupérer les captures faites dans le motif de la RewriteRule.

Si l'utilisateur demande par exemple /contact, la variable peut contenir une valeur proche de GET /contact HTTP/1.1.

Les variables d'environnement liées aux redirections

Apache crée aussi lui-même certaines variables d'environnement. C'est notamment le cas lorsqu'une directive ErrorDocument pointe vers une URL locale du site. Dans ce cas, Apache effectue un traitement interne et crée des variables commençant par REDIRECT_.

Parmi elles, REDIRECT_STATUS contient le code d'erreur à l'origine de cet affichage interne, par exemple 401, 403 ou 404.

Cette variable est utile lorsqu'on veut empêcher un accès direct à une page d'erreur personnalisée, tout en laissant Apache l'utiliser normalement lorsqu'une vraie erreur se produit.

Prenons deux situations. Si l'utilisateur saisit directement /vues/ma-page-404-perso.php dans la barre d'adresse, il ne s'agit pas d'un affichage d'erreur déclenché par Apache. La variable %{ENV:REDIRECT_STATUS} n'est donc pas renseignée.

En revanche, si l'utilisateur demande une URL inexistante et qu'Apache affiche ensuite cette même page via ErrorDocument 404, il s'agit cette fois d'un traitement interne. La variable %{ENV:REDIRECT_STATUS} contient alors 404.


                    RewriteEngine On

                    # Si une page d'erreur personnalisée est demandée directement,
                    # on renvoie une erreur 404.
                    # En revanche, si Apache l'appelle via ErrorDocument,
                    # REDIRECT_STATUS contient déjà le code de l'erreur et la page peut s'afficher.
                    RewriteCond %{ENV:REDIRECT_STATUS} ^$
                    RewriteRule ^ma-page-40[134]-perso\.php$ - [R=404,L]
                

Cette règle signifie si l'URL demandée vise directement une page d'erreur personnalisée et qu'Apache n'est pas en train de l'afficher dans le cadre d'une erreur interne, alors renvoyer une erreur 404.

La première condition vérifie que l'on vise bien l'une des pages d'erreur personnalisées. La seconde vérifie que %{ENV:REDIRECT_STATUS} est vide. Autrement dit, Apache n'est pas en train d'afficher cette page comme conséquence normale d'une erreur.

Sans ce test, la règle bloquerait aussi l'affichage interne de la page d'erreur, alors qu'on veut seulement empêcher les accès directs du navigateur.

Accéder à la variable en PHP

Une variable d'environnement créée par Apache peut aussi être récupérée en PHP, généralement via le tableau $_SERVER.


                    <?php
                    echo $_SERVER['MA_VARIABLE'];
                    ?>
                

Cela permet à un script PHP de récupérer une information préparée plus tôt par une règle de réécriture.

Contrôler l'accès à certaines ressources

Jusqu'ici, nous avons surtout utilisé .htaccess pour rediriger des URL ou réécrire des chemins. Un fichier .htaccess peut aussi servir à contrôler ce qu'Apache a le droit d'afficher et à décider qui peut accéder à certaines ressources.

Dans cette partie, il faut bien distinguer plusieurs situations. Empêcher Apache d'afficher automatiquement la liste d'un dossier ne revient pas à interdire l'accès aux fichiers qu'il contient. Interdire l'accès à une ressource ne revient pas non plus à protéger cette ressource par mot de passe.

Empêcher l'affichage automatique du contenu d'un dossier

Lorsqu'un dossier ne contient pas de fichier d'index, Apache peut parfois afficher automatiquement la liste de son contenu. Pour empêcher cet affichage, on utilise la directive suivante.


                    Options -Indexes
                

Cette directive empêche seulement l'affichage automatique du contenu du dossier. Elle ne bloque pas l'accès direct à un fichier si son URL exacte est connue.

Par exemple, si le dossier /images/ ne contient pas de fichier d'index, Apache n'affichera plus automatiquement la liste de ses fichiers. En revanche, une image comme /images/logo.png restera accessible si son chemin exact est demandé.

À l'inverse, on peut autoriser explicitement cet affichage avec


                    Options +Indexes
                

Interdire complètement l'accès à une ressource

Si l'objectif n'est plus seulement de masquer la liste d'un dossier, mais de refuser tout accès, Apache 2.4 recommande d'utiliser la directive suivante.


                    Require all denied
                

Cette directive interdit l'accès à la ressource et Apache renvoie alors une erreur 403 Forbidden.

Autrement dit, Options -Indexes dit seulement ne montre pas automatiquement le contenu du dossier, alors que Require all denied dit personne n'a le droit d'y accéder.

Il existe aussi les directives Order, Deny et Allow, qui permettent d'obtenir des effets équivalents. Elles appartiennent toutefois à l'ancienne syntaxe et sont déconseillées depuis Apache 2.4.

Autoriser seulement certaines adresses IP

Il est aussi possible de n'autoriser l'accès qu'à certaines machines seulement. On crée alors une liste d'adresses IP autorisées. Toute machine qui n'est pas dans cette liste reçoit un refus d'accès.

Apache accepte plusieurs façons d'écrire une restriction IP. Il est possible d'utiliser une adresse IP complète, une adresse partielle, un couple réseau/masque ou une notation CIDR.

Une adresse IP complète vise une machine précise. Dans l'exemple suivant, on autorise uniquement l'adresse ip 192.168.1.20 autorise uniquement cette adresse.


                    Require ip 192.168.1.20
                

Une adresse partielle permet de viser un début d'adresse commun. Dans l'exemple suivant, on autorise toutes les adresses qui commencent par 192.168.1..


                    Require ip 192.168.1
                

Un couple réseau/masque écrit d'abord l'adresse du réseau, puis le masque qui indique quelle partie de l'adresse IP appartient au réseau. Dans l'exemple suivant, on autorise toutes les adresses du réseau 192.168.1.0/255.255.255.0, c'est-à-dire, en pratique, les adresses qui commencent par 192.168.1.


                    Require ip 192.168.1.0/255.255.255.0
                

La notation CIDR est une écriture plus compacte du même principe. Le nombre après le slash indique combien de bits, en partant du début de l'adresse, appartiennent à la partie réseau. Dans l'exemple suivant, /24 signifie que les 24 premiers bits désignent le réseau. Cette écriture autorise donc le même ensemble d'adresses que 192.168.1.0/255.255.255.0.


                    Require ip 192.168.1.0/24
                

Autrement dit, les écritures Require ip 192.168.1, Require ip 192.168.1.0/255.255.255.0 et Require ip 192.168.1.0/24 visent ici la même plage d'adresses.

Il est aussi possible d'autoriser plusieurs adresses IP ou plusieurs plages d'adresses. Il suffit de les indiquer dans la même directive Require ip, en les séparant par un espace.


                    Require ip 127.0.0.1 192.168.1.20 192.168.1.21
                

Protéger un dossier par mot de passe

Apache peut aussi protéger un dossier en demandant un nom d'utilisateur et un mot de passe. Le mécanisme le plus simple à découvrir est l'authentification HTTP Basic.


                    AuthType Basic
                    AuthName "Zone protégée"
                    AuthUserFile C:/chemin/vers/un/dossier/non-public/.htpasswd
                    Require valid-user
                

Ici, AuthType Basic active l'authentification Basic, AuthName définit le texte affiché dans la boîte de dialogue, AuthUserFile indique où se trouve le fichier des identifiants, et Require valid-user signifie que tout utilisateur présent dans ce fichier est autorisé à entrer, à condition d'avoir fourni le bon mot de passe.

Pour obtenir le chemin absolu du fichier .htpasswd, créer temporairement un petit fichier PHP à un endroit accessible du projet, par exemple à la racine. Dans ce fichier, utiliser realpath() avec __DIR__ pour afficher le chemin réel du dossier private/auth. Afficher ensuite cette page dans le navigateur, copier le chemin obtenu, puis y ajouter le nom du fichier .htpasswd.

Le fichier .htpasswd doit être placé dans un emplacement non accessible depuis le web. Il ne faut donc pas l'enregistrer dans un dossier publiquement servi par Apache.

Pour créer ce fichier avec Apache, on peut utiliser l'outil en ligne de commande htpasswd. Le drapeau -c sert à créer un nouveau fichier.


                    htpasswd -c C:/chemin/vers/un/dossier/non-public/.htpasswd Claudy
                

Apache demande alors le mot de passe, puis une confirmation. Si le fichier existe déjà et que l'on veut simplement ajouter un autre utilisateur, il faut relancer la commande sans -c.


                    htpasswd C:/chemin/vers/un/dossier/non-public/.htpasswd Chri
                

En production, ce mécanisme doit être utilisé avec HTTPS. En effet, avec l'authentification Basic, le mot de passe est transmis sans chiffrement propre au mécanisme lui-même. La connexion doit donc être protégée par SSL/TLS.

Notez aussi qu'avec l'authentification HTTP Basic, il n'existe pas de mécanisme de déconnexion standard comparable à une session PHP. Le navigateur garde généralement les identifiants en mémoire et les renvoie automatiquement aux requêtes suivantes. Pour refaire un test, il faut fermer complètement le navigateur, ou forcer l'envoi d'identifiants volontairement incorrects dans l'URL. Par exemple, pour une zone protégée située à l'adresse http://localhost/mon-projet/zone-protegee-par-mdp/, on peut tester l'adresse http://login-bidon:mdp-bidon@localhost/mon-projet/zone-protegee-par-mdp/. Cette astuce peut forcer le navigateur à rejeter l'authentification en cours et à redemander des identifiants. Elle sert uniquement à faciliter les tests en local.

Mise en cache des ressources

La mise en cache consiste à indiquer au navigateur qu'il peut conserver certaines ressources pendant une durée donnée, par exemple des images, des feuilles de style ou des scripts.


                    <IfModule mod_headers.c>
                        <FilesMatch "\.(webp|png|jpg|svg|ico|css|js|mp4|ttf|woff2|webmanifest)$">
                            Header set Cache-Control "max-age=31536000, public"
                        </FilesMatch>
                    </IfModule>
                

Une durée d'un an convient surtout aux fichiers dont le nom change quand leur contenu change. Sinon, le navigateur risque de garder une version dépassée trop longtemps.

En développement, on préfère souvent désactiver ou limiter fortement le cache.


                    <IfModule mod_headers.c>
                        <FilesMatch "\.(css|js)$">
                            Header set Cache-Control "no-store, no-cache, must-revalidate"
                            Header set Pragma "no-cache"
                            Header set Expires "0"
                        </FilesMatch>
                    </IfModule>
                

À propos de <IfModule>

La directive <IfModule> permet d'exécuter un bloc seulement si un module précis d'Apache est disponible. Ici, on l'utilise avec mod_headers, car les directives Header ne fonctionnent que si ce module est chargé. Si le module n'est pas présent, Apache ignore simplement le contenu du bloc.

Utiliser <IfModule> est confortable pour des fonctionnalités secondaires comme le cache ou certains en-têtes.

En revanche, lorsqu'un projet dépend fortement de mod_rewrite, encapsuler toutes les règles dans un <IfModule mod_rewrite.c> peut masquer un problème important. Si le module manque, Apache n'affichera parfois aucune erreur claire et les URL ne fonctionneront simplement pas comme prévu.

Quand la réécriture d'URL est indispensable au fonctionnement du site, il est souvent préférable de laisser l'erreur apparaître plutôt que de la masquer.

Ordre des directives dans un fichier .htaccess

Apache lit les directives de haut en bas. L'ordre n'est donc pas une simple question de présentation. Il influence directement le résultat.

Apache n'impose pas un plan unique, mais un ordre logique aide à éviter les conflits.

  1. activation des fonctionnalités nécessaires, par exemple RewriteEngine On
  2. redirections externes qui doivent être visibles par le navigateur
  3. réécritures internes
  4. restrictions d'accès et règles de sécurité
  5. pages d'erreur personnalisées
  6. directives plus générales comme le cache ou Options -Indexes

Dans un projet réel, il faut aussi garder à l'esprit qu'une règle placée plus haut peut empêcher les suivantes de s'appliquer.

Exercices

Exo-htaccess-01 : Contrôler l'affichage du contenu des dossiers

Objectif

Comprendre que le serveur peut parfois afficher la liste des fichiers contenus dans un dossier lorsqu'aucun fichier d'accueil n'est trouvé, puis apprendre à contrôler ce comportement avec la configuration Apache.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Modifier volontairement l'adresse dans la barre d'adresse pour cibler directement le dossier assets/images.
  3. Regarder le résultat obtenu.
  4. Selon la configuration d'Apache, le serveur peut afficher la liste des fichiers du dossier, un peu comme dans un explorateur de fichiers. Ce comportement peut poser problème, car il permet à l'utilisateur de parcourir directement le contenu d'un dossier du projet.
  5. Créer un fichier .htaccess à la racine du projet.
  6. Ce fichier est placé à la racine afin que les directives ajoutées s'appliquent au dossier racine et à l'ensemble de ses sous-dossiers.
  7. Dans ce fichier, utiliser la directive Apache permettant de modifier l'option Indexes afin d'empêcher l'affichage automatique du contenu des dossiers.
  8. Enregistrer les modifications.
  9. Refaire exactement le même test en ciblant directement le dossier assets/images.
  10. Comparer le résultat avec celui du premier test.
  11. Tester également un autre dossier du projet qui ne contient pas de fichier d'accueil.
  12. Constater que la directive placée dans le fichier .htaccess racine s'applique aussi aux sous-dossiers.
  13. Retenir que l'option Indexes permet de contrôler l'affichage automatique du contenu d'un dossier lorsqu'aucun fichier d'accueil n'est trouvé.
  14. Retenir aussi qu'il est possible de modifier ce comportement plus localement en plaçant un autre fichier .htaccess dans le dossier concerné. La directive placée dans ce dossier s'applique alors à ce dossier et à ses propres sous-dossiers.

Exo-htaccess-02 : Bloquer l'accès direct au dossier private

Objectif

Comprendre pourquoi certains fichiers internes du projet ne doivent pas être accessibles directement depuis le navigateur, puis configurer le serveur pour bloquer les requêtes client vers le dossier private.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Modifier volontairement l'adresse dans la barre d'adresse pour cibler directement le fichier private/template/header.php.
  3. Regarder le résultat obtenu.
  4. Le fichier header.php peut s'afficher comme une page indépendante, alors qu'il sert seulement de morceau de template pour construire les pages du site.
  5. Ce comportement pose problème, car les fichiers du dossier private font partie du fonctionnement interne du projet. Ils peuvent contenir des morceaux de template, de la configuration, de la logique PHP ou d'autres fichiers utiles au serveur, mais ils ne doivent pas être appelés directement par l'utilisateur depuis une URL.
  6. Créer un fichier .htaccess dans le dossier private.
  7. Ce fichier est placé directement dans private afin que la protection s'applique à ce dossier et à l'ensemble de ses sous-dossiers.
  8. Dans ce fichier .htaccess, utiliser la directive Apache permettant d'interdire l'accès aux requêtes client.
  9. Enregistrer les modifications.
  10. Refaire exactement le même test en ciblant directement le fichier private/template/header.php.
  11. Tester également l'accès direct à un autre fichier situé dans private.
  12. Si la protection est correctement configurée, les fichiers du dossier private ne s'affichent plus directement dans le navigateur. Le serveur doit répondre avec une erreur 403 Forbidden, car les ressources existent, mais leur accès est interdit aux requêtes client.

Exo-htaccess-03 : Configurer une page personnalisée pour les erreurs 403

Objectif

Comprendre que le blocage du dossier private provoque une erreur 403 Forbidden, puis configurer une page d'erreur personnalisée afin de conserver la mise en page, la navigation et l'identité visuelle du site.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Modifier volontairement l'adresse dans la barre d'adresse pour cibler directement un fichier situé dans le dossier private.
  3. Cibler par exemple le fichier private/template/header.php.
  4. Regarder le résultat affiché par défaut par le serveur.
  5. Le serveur répond avec une erreur 403 Forbidden, car la ressource existe, mais son accès est interdit.
  6. On rencontre alors le même problème que pour une page 404 générique. La page d'erreur est produite par le serveur, et non par le site. Elle ne reprend pas la mise en page, les couleurs, les typographies ni le menu du projet. L'utilisateur se retrouve sur une page qui semble extérieure au site, avec moins de repères et moins de possibilités pour continuer sa navigation.
  7. Dans le fichier .htaccess placé à la racine du projet, ajouter la directive Apache ErrorDocument permettant d'associer l'erreur 403 à une page personnalisée.
  8. Utiliser comme page personnalisée le fichier fourni avec le gabarit de l'exercice : /errors/ma-page-403-perso.php.
  9. Enregistrer les modifications.
  10. Refaire exactement le même test en ciblant directement un fichier situé dans private.
  11. Comparer le résultat avec celui du premier test.
  12. Si la directive est correctement rédigée, la page d'erreur 403 personnalisée s'affiche et conserve cette fois la navigation, la mise en page et l'identité visuelle du site.

Exo-htaccess-04 : Configurer une page personnalisée pour les erreurs 404

Objectif

Comprendre que le même problème se produit lorsqu'une ressource demandée n'existe pas, puis configurer une page d'erreur 404 personnalisée afin de conserver la mise en page, la navigation et l'identité visuelle du site.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Modifier volontairement l'adresse dans la barre d'adresse pour demander une ressource inexistante sur le même domaine.
  3. Cibler par exemple une page inexistante, une image inexistante ou un fichier absent du projet.
  4. Regarder le résultat affiché par défaut par le serveur.
  5. Le serveur répond avec une erreur 404 Not Found, car la ressource demandée n'existe pas.
  6. On rencontre le même problème que pour l'erreur 403 par défaut. La page d'erreur est générique. Elle est produite par le serveur, et non par le site. Elle ne reprend pas la mise en page, les couleurs, les typographies ni le menu du projet.
  7. Dans le fichier .htaccess placé à la racine du projet, ajouter la directive Apache ErrorDocument permettant d'associer l'erreur 404 à une page personnalisée.
  8. Utiliser comme page personnalisée le fichier fourni avec le gabarit de l'exercice : /errors/ma-page-404-perso.php.
  9. Enregistrer les modifications.
  10. Refaire exactement le même test avec la même ressource inexistante.
  11. Comparer le résultat avec celui du premier test.
  12. Si la directive est correctement rédigée, la page d'erreur 404 personnalisée s'affiche et conserve cette fois la navigation, la mise en page et l'identité visuelle du site.

Exo-htaccess-05 : Bloquer l'accès direct aux pages d'erreur personnalisées

Objectif

Comprendre que les pages d'erreur personnalisées ne doivent pas être affichées comme des pages normales, puis configurer le fichier .htaccess racine pour qu'elles soient utilisées uniquement lorsqu'une vraie erreur HTTP survient.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Modifier volontairement l'adresse dans la barre d'adresse pour cibler directement la page /errors/ma-page-403-perso.php.
  3. Refaire le même test avec la page /errors/ma-page-404-perso.php.
  4. Regarder le résultat obtenu.
  5. Les fichiers du dossier private sont maintenant protégés, mais les pages d'erreur personnalisées restent accessibles directement depuis leur URL.
  6. Ce comportement pose un problème similaire à celui rencontré avec les fichiers de template. Ces pages servent à afficher une réponse lorsqu'une erreur HTTP survient. Elles ne sont pas destinées à être consultées comme des pages normales du site.
  7. Ne pas déplacer ces pages dans le dossier private.
  8. Les pages ciblées par ErrorDocument doivent rester utilisables par Apache comme documents d'erreur. Si elles sont placées dans un dossier entièrement bloqué, le serveur risque de ne plus pouvoir les utiliser correctement lorsqu'une erreur survient.
  9. Dans le fichier .htaccess placé à la racine du projet, activer le moteur de réécriture d'URL si ce n'est pas encore fait.
  10. Ajouter ensuite une règle qui bloque les accès directs aux pages d'erreur personnalisées déjà utilisées dans les exercices précédents.
  11. Cette règle doit vérifier que la page n'est pas appelée à la suite d'une vraie erreur HTTP.
  12. Pour réaliser ce test, utiliser la variable transmise par Apache lorsqu'un document d'erreur est appelé via ErrorDocument.
  13. Si cette variable ne contient aucun statut d'erreur, cela signifie que le navigateur demande directement la page d'erreur personnalisée comme une page normale.
  14. Dans ce cas, Apache doit renvoyer une erreur 404 Not Found.
  15. Placer cette règle après l'activation du moteur de réécriture, mais avant les futures règles générales de réécriture d'URL.
  16. Enregistrer les modifications.
  17. Refaire le test en ciblant directement /errors/ma-page-403-perso.php.
  18. Refaire le test en ciblant directement /errors/ma-page-404-perso.php.
  19. Si la protection est correctement configurée, ces pages ne sont plus accessibles comme des pages normales. Une requête directe doit produire une erreur 404 Not Found.
  20. Refaire ensuite un test réel en ciblant une ressource inexistante du site.
  21. Refaire aussi un test réel en ciblant un fichier situé dans private.
  22. Vérifier que les pages personnalisées s'affichent encore lorsqu'elles sont appelées à la suite d'une vraie erreur 403 ou 404.

Exo-htaccess-06 : Protéger un dossier par mot de passe

Objectif

Comprendre comment limiter l'accès à un dossier du site avec une authentification HTTP Basic, puis vérifier que seuls les utilisateurs enregistrés dans un fichier de mots de passe peuvent consulter son contenu.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Cliquer sur le lien de navigation qui mène vers le dossier zone-protegee-par-mdp.
  3. Regarder le résultat obtenu.
  4. Le dossier est accessible directement. Pour l'instant, n'importe quel utilisateur qui connaît l'adresse de cette zone peut l'afficher dans son navigateur.
  5. Créer d'abord le fichier qui contiendra les utilisateurs autorisés à accéder à cette zone.
  6. Placer ce fichier dans le dossier private/auth. Le fichier contient les utilisateurs autorisés et les mots de passe hachés. Il doit donc rester accessible au serveur pour vérifier les connexions, mais inaccessible directement depuis une URL du navigateur.
  7. Utiliser la commande htpasswd dans le terminal pour créer ce fichier avec un premier nom d'utilisateur et un premier mot de passe.
  8. Ouvrir le fichier généré et observer son contenu.
  9. Le nom d'utilisateur est visible, mais le mot de passe n'est pas stocké en clair. La commande l'a transformé en version hachée avant de l'enregistrer.
  10. Créer ensuite un fichier .htaccess dans le dossier zone-protegee-par-mdp.
  11. Ce fichier est placé directement dans le dossier à protéger afin que les directives d'authentification s'appliquent à cette zone et à ses éventuels sous-dossiers.
  12. Dans ce fichier .htaccess, ajouter les directives Apache nécessaires à l'authentification HTTP Basic.
  13. La configuration doit indiquer le type d'authentification, le nom de la zone protégée affiché dans la fenêtre de connexion, le chemin vers le fichier contenant les utilisateurs autorisés et la règle qui accepte les utilisateurs valides.
  14. Faire attention au chemin utilisé pour cibler le fichier des mots de passe. La directive doit pointer vers le chemin réel du fichier sur le serveur, pas vers son adresse dans le navigateur.
  15. Enregistrer les modifications.
  16. Revenir dans le navigateur et ouvrir à nouveau la page zone-protegee-par-mdp.
  17. Si la configuration est correcte, le navigateur demande maintenant un nom d'utilisateur et un mot de passe avant d'afficher la zone protégée.
  18. Se connecter avec le premier identifiant créé.
  19. Si l'accès est accepté, la protection fonctionne.
  20. Le navigateur peut ensuite garder l'authentification active pendant la session. Pour continuer les tests, fermer complètement le navigateur ou ouvrir une fenêtre de navigation privée afin de repartir sans être déjà connecté.
  21. Retourner dans le terminal.
  22. Ajouter un deuxième utilisateur au même fichier de mots de passe avec htpasswd.
  23. Cette fois, ne pas utiliser l'option qui crée un nouveau fichier. Cette option sert lors de la première création et risquerait d'écraser les utilisateurs déjà enregistrés.
  24. Refaire le test dans le navigateur en repartant d'une session non connectée.
  25. Se connecter avec le deuxième nom d'utilisateur et son mot de passe.
  26. Si l'accès est accepté avec ce nouveau compte, le fichier de mots de passe contient bien plusieurs utilisateurs autorisés.

Exo-htaccess-07 : Configurer une page personnalisée pour les erreurs 401

Objectif

Comprendre que la protection par mot de passe peut provoquer une erreur 401 Unauthorized, puis configurer une page d'erreur personnalisée afin de conserver la mise en page, la navigation et l'identité visuelle du site.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Cliquer sur le lien de navigation qui mène vers le dossier zone-protegee-par-mdp.
  3. Lorsque le navigateur demande un nom d'utilisateur et un mot de passe, entrer volontairement des identifiants incorrects.
  4. Si la fenêtre de connexion s'affiche à nouveau, annuler la demande d'authentification.
  5. Regarder le résultat affiché par défaut par le serveur.
  6. Le serveur répond avec une erreur 401 Unauthorized, car l'accès à la ressource demande une authentification valide.
  7. On rencontre le même problème que pour les erreurs 403 et 404 par défaut. La page d'erreur est générique. Elle est produite par le serveur, et non par le site. Elle ne reprend pas la mise en page, les couleurs, les typographies ni le menu du projet.
  8. Dans le fichier .htaccess placé à la racine du projet, ajouter la directive Apache ErrorDocument permettant d'associer l'erreur 401 à une page personnalisée.
  9. Utiliser comme page personnalisée le fichier fourni avec le gabarit de l'exercice : /errors/ma-page-401-perso.php.
  10. Actualiser ensuite la règle qui bloque l'accès direct aux pages d'erreur personnalisées dans le fichier .htaccess placé à la racine du projet.
  11. Cette règle protégeait déjà les pages personnalisées des erreurs 403 et 404.
  12. La mettre à jour pour inclure aussi la page personnalisée de l'erreur 401.
  13. La page ma-page-401-perso.php doit donc être inaccessible lorsqu'elle est demandée directement depuis le navigateur, mais elle doit rester utilisable lorsqu'Apache l'appelle à la suite d'une vraie erreur 401.
  14. Enregistrer les modifications.
  15. Refaire le test en ciblant le dossier zone-protegee-par-mdp.
  16. Entrer à nouveau des identifiants incorrects, puis annuler la demande d'authentification si le navigateur la propose encore.
  17. Comparer le résultat avec celui du premier test.
  18. Si la configuration est correcte, la page d'erreur 401 personnalisée s'affiche et conserve cette fois la navigation, la mise en page et l'identité visuelle du site.
  19. Tester ensuite l'accès direct à /errors/ma-page-401-perso.php.
  20. Si la protection est correctement configurée, cette page ne s'affiche pas comme une page normale. Une requête directe doit produire une erreur 404 Not Found.
  21. Tester à nouveau les erreurs 403 et 404 pour vérifier que leurs pages personnalisées fonctionnent encore.

Exo-htaccess-08 : Limiter l'accès à une page selon l'adresse IP

Objectif

Comprendre comment Apache peut autoriser ou refuser l'accès à une ressource selon l'adresse IP du visiteur, puis configurer l'accès à une page pour qu'elle soit consultable uniquement depuis une adresse IP autorisée.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Afficher la page afficher-ip.php.
  3. Regarder l'adresse IP affichée par la page.
  4. Cette adresse correspond à l'adresse IP vue par le serveur pour la requête en cours. En local, elle peut par exemple ressembler à 127.0.0.1 ou ::1.
  5. Copier cette adresse IP dans un fichier texte, ou la placer temporairement en commentaire dans votre code, afin de pouvoir la réutiliser plus tard.
  6. Dans le fichier .htaccess placé à la racine du projet, ajouter une configuration qui concerne uniquement le fichier afficher-ip.php.
  7. Dans cette configuration, utiliser la directive Apache permettant d'autoriser uniquement une adresse IP précise.
  8. Pour le premier test, indiquer volontairement une adresse IP bidon, différente de celle copiée plus tôt.
  9. Enregistrer les modifications.
  10. Recharger la page afficher-ip.php dans le navigateur.
  11. Si la configuration est correcte, la page ne doit plus être accessible. Le serveur doit répondre avec une erreur 403 Forbidden, car votre adresse IP ne fait pas partie des adresses autorisées.
  12. Retourner dans le fichier .htaccess placé à la racine du projet.
  13. Remplacer l'adresse IP bidon par l'adresse IP copiée au début de l'exercice.
  14. Enregistrer les modifications.
  15. Recharger à nouveau la page afficher-ip.php.
  16. Si la configuration est correcte, la page redevient accessible, car votre adresse IP correspond maintenant à celle autorisée par Apache.
  17. Retenir que cette protection ne dépend pas d'un mot de passe, mais de l'adresse IP vue par le serveur au moment de la requête.

Exo-htaccess-09 : Faire fonctionner les URL sans extension .php

Objectif

Comprendre qu'une URL affichée dans le navigateur peut être différente du fichier réellement exécuté par le serveur, puis configurer Apache pour utiliser des URL plus propres sans extension .php.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Tester une page existante avec son extension .php, par exemple contact.php.
  3. Tester ensuite la même page sans son extension, par exemple contact.
  4. Regarder le résultat obtenu.
  5. Pour l'instant, la page avec l'extension fonctionne, mais la version sans extension provoque normalement une erreur 404 Not Found.
  6. Dans le fichier .htaccess placé à la racine du projet, vérifier que le moteur de réécriture d'URL d'Apache est activé.
  7. Si la directive RewriteEngine On est déjà présente, ne pas l'ajouter une deuxième fois.
  8. Ajouter ensuite une règle générique qui permet de charger automatiquement le fichier .php correspondant lorsqu'une URL sans extension est demandée.
  9. Cette règle doit fonctionner uniquement si le fichier PHP correspondant existe réellement.
  10. Cette règle doit aussi éviter les vrais fichiers et les vrais dossiers, afin de ne pas intercepter les images, les feuilles de style, les scripts JavaScript ou les dossiers existants.
  11. Exclure également les dossiers errors et private de cette réécriture générique.
  12. Le dossier errors contient des pages utilisées par ErrorDocument. Le dossier private contient des fichiers internes déjà protégés par son propre fichier .htaccess.
  13. Placer cette règle après les règles plus spécifiques déjà présentes dans le fichier .htaccess racine.
  14. L'ordre est important. Les règles qui bloquent des accès particuliers doivent être testées avant la règle générale qui transforme une URL propre en fichier PHP.
  15. Enregistrer les modifications.
  16. Refaire le test avec la page sans extension, par exemple contact.
  17. Si la configuration est correcte, l'URL sans extension fonctionne. Le navigateur affiche une adresse plus propre, tandis que le serveur exécute le fichier PHP correspondant en interne.
  18. Dans le fichier private/template/header.php, mettre à jour les liens du menu afin qu'ils utilisent les URL sans extension .php.
  19. Tester la navigation du site depuis le menu.
  20. À ce stade, les URL sans extension fonctionnent, mais les URL avec .php fonctionnent encore aussi.
  21. Une même page peut donc encore être accessible avec deux adresses différentes, par exemple contact et contact.php.
  22. Tester aussi l'adresse index.
  23. À ce stade, cette adresse peut encore charger la page d'accueil en interne via le fichier index.php.
  24. Dans l'exercice suivant, on empêchera les accès directs aux fichiers .php publics et on empêchera aussi index de devenir une URL publique de la page d'accueil.
  25. Pour un site déjà en ligne, on utiliserait généralement des redirections permanentes afin de renvoyer les anciennes URL vers les nouvelles sans perdre les liens existants.
  26. Dans cet exercice, on part du principe que le projet n'est pas encore en production. On choisira donc plutôt de rendre les anciennes URL inaccessibles dans l'exercice suivant.

Exo-htaccess-10 : Interdire les accès directs aux fichiers .php publics

Objectif

Comprendre qu'un fichier PHP peut être utilisé par le serveur sans être exposé comme une URL publique, puis configurer Apache pour refuser les accès directs aux fichiers .php publics et à l'URL propre index.

Instructions

  1. Ouvrir le site dans le navigateur.
  2. Tester une page avec son URL propre, par exemple contact.
  3. Tester ensuite la même page avec son fichier PHP visible dans l'URL, par exemple contact.php.
  4. Tester aussi la page d'accueil avec l'adresse du dossier, puis avec index.php et index.
  5. Regarder le résultat obtenu.
  6. Pour l'instant, les fichiers .php publics peuvent encore être demandés directement depuis le navigateur.
  7. L'adresse index peut aussi devenir une URL publique de la page d'accueil, car la règle d'URL propre peut la réécrire en interne vers index.php.
  8. Ce comportement crée des doublons inutiles. Par exemple, la page d'accueil peut être accessible avec l'adresse du dossier, avec index.php et avec index.
  9. Dans le fichier .htaccess placé à la racine du projet, ajouter une règle qui refuse l'adresse index demandée directement par le navigateur.
  10. Cette règle doit être placée avant la réécriture interne vers les fichiers PHP.
  11. Si le navigateur demande explicitement index, Apache doit répondre avec une erreur 404 Not Found.
  12. Ajouter ensuite une règle qui refuse les accès directs aux fichiers .php publics.
  13. Cette règle doit détecter les requêtes dans lesquelles l'utilisateur demande directement un fichier avec l'extension .php.
  14. Pour éviter de bloquer les réécritures internes, s'appuyer sur la requête initiale envoyée par le navigateur, et non uniquement sur le fichier finalement utilisé par Apache.
  15. La règle doit viser les fichiers .php publics du site, mais elle ne doit pas bloquer les dossiers qui ont déjà une logique d'accès particulière.
  16. Exclure notamment le dossier errors, car ses pages d'erreur personnalisées sont déjà gérées par une règle spécifique et doivent rester utilisables par Apache lorsqu'une erreur est déclenchée avec ErrorDocument.
  17. Exclure également le dossier private, car son accès est déjà géré séparément par son propre fichier .htaccess.
  18. Si une requête directe vers un fichier .php public est détectée, Apache doit répondre avec une erreur 404 Not Found.
  19. Vérifier l'ordre des règles dans le fichier .htaccess racine.
  20. Les directives ErrorDocument doivent rester au début de la configuration.
  21. La directive RewriteEngine On doit être présente avant les règles de réécriture.
  22. Les règles qui bloquent des accès particuliers doivent être placées avant la règle générale qui transforme les URL propres en fichiers PHP.
  23. La règle générale de réécriture vers les fichiers .php doit rester après ces règles de blocage.
  24. Enregistrer les modifications.
  25. Refaire le test avec une URL propre, par exemple contact.
  26. Si la configuration est correcte, cette URL fonctionne toujours.
  27. Refaire ensuite le test avec le fichier PHP visible dans l'URL, par exemple contact.php.
  28. Si la configuration est correcte, cette adresse produit maintenant une erreur 404 Not Found.
  29. Tester ensuite index.php.
  30. Cette adresse doit produire une erreur 404 Not Found.
  31. Tester enfin index.
  32. Cette adresse doit aussi produire une erreur 404 Not Found.
  33. La page d'accueil doit rester accessible avec l'adresse du dossier.
  34. Tester à nouveau une page d'erreur personnalisée en provoquant une vraie erreur 404.
  35. Vérifier que la page personnalisée du dossier errors fonctionne encore lorsqu'elle est appelée par Apache.
  36. Tester à nouveau l'accès direct à une page d'erreur personnalisée, par exemple /errors/ma-page-404-perso.php.
  37. Vérifier que cette page reste inaccessible lorsqu'elle est demandée directement depuis le navigateur.
  38. Tester à nouveau un fichier situé dans private.
  39. Vérifier que ce dossier reste protégé par son propre mécanisme et qu'il produit toujours le comportement attendu.
  40. Retenir que les fichiers PHP publics restent utilisés par le serveur, mais qu'ils ne sont plus exposés comme des URL publiques.