Laravel: Faire un accés non connecté via un ID

On a souvent besoin de faire un accés non connecté à certaines resources d'une application. Par exemple imaginons le téléchargement d'un document avec un lien envoyé par email a un utilisateur. Il peut être judicieux d'un point de vue UX que le téléchargement se déclenche directement au clic sur le lien, même si l'utilisateur n'est pas connecté.

Pour ce faire, on a donc besoin d'un lien contenant les information nécéssaires pour trouver quoi télécharger, mais aussi que ce lien soit impossible à deviner et à modifier.

La solution naive consistera a utiliser un hash, sauf que ce hash a besoin d'être stocké quelque part sur le serveur pour trouver une correspondance et remonter a la cible (le fichier a télécharger).

Une solution plus complexe consiste à utiliser les méthodes encrypt et decrypt de laravel. Ces méthodes permettent, à partir de données texte, de chiffrer et/ou déchiffrer de maniére à les rendre illisibles. Petit bonus, le contenu chiffré est aussi signé de maniére a rendre invalide toute tentative de modification du contenu.

La fonction suivante permet de créer une clé en fonction de données arbitraires:

function encryptKeyForUrl($key) {
		$key = json_encode($key);
		$key = encrypt($key);
		$key = base64_encode($key);
		return $key;
	}

Le json encode permet de transformer les données en string, le encrypt permet de les chiffrer et de les signer, et enfin le base64 encode rend ces données propices à une utilisation dans une url.

Enfin, la fonction pour récupérer ces données:

function decryptKeyInUrl($key) {
		$key = base64_decode($key);
		$key = decrypt($key);
		$key = json_decode($key);
		return $key;
	}

Même fonctionnement en sens inverse, le base64decode récupére la string non "urlifiée", on la déchiffre (en vérrifiant la signature) et enfin on jsondecode pour retrouver les données utilisables directement.

L'usage se fait par un simple appel en début de contrôlleur:

$data = decryptKeyInUrl($request->get('key'));
// Usage
$data->someKey

Cette technique est en théorie sécurisée car:

  • La string exposée dans l'url est suffisament longue pour être difficilement devinable
  • Le contenu est chiffré (illisible) et signé (impossible à modifier). Les chances d'avoir, via une attaque par bruteforce, une signature à la fois déchiffrable, avec une signature valide, et qui contient des informations valides est minime.
  • La fonction encrypt/decrypt utilise la clé APP_KEY de votre laravel. C'est à dire que vos environements générerons des signatures différentes même entre vos environnements de prod et de préprod.

Toutefois il y a des facteurs qui rendent cette approche moins sécurisée:

  • En cas d'erreur de configuration https, l'url peut se retrouver exposée dans les logs d'un des proxys ou des serveurs qui font transiter la requéte.
  • L'url peut se retrouver exposée via un marque page
  • L'url peut se retrouver exposée via l'historique de l'ordinateur du client
  • L'url peut se retrouver exposée si la boite email du destinataire est compromise.
  • Si vous stockez trop d'informations, il est possible que la taille de l'url dépasse le maximum supporté par certains navigateurs (looking at you IE!). Transmettez uniquement les IDS et recoupez avec les données en base.

Comme toujours en matiére d'UX, c'est

Qu'avez vous pensé de cet article?