DEV Community

chemisax
chemisax

Posted on

¿Cómo guarda Laravel la cookie de sesión y csrf?

Hoy tuve un problema con una SPA que se comunica con un backend en Laravel. Son contenedores de docker, por lo que en teoría deberían funcionar igual, pero en otra computadora, la comunicación con el backend fallaba:

CSRF token mismatch

Después de revisar todas las configuraciones de la sesión, de sanctum, de las cookies, no daba con la razón por la que Laravel no reconocía el token. Así que para debuggear, me puse a leer el código de Laravel para entender cómo se genera la sesión, su cookie y el token de csrf.

Por cierto, estoy usando el driver database para guardar la sesión.

La sesión

Tabla sessions

Si usas los ajustes por default:

  • La sesión se guarda en la tabla sessions
  • El ID son 40 caracteres aleatorios creados con la función Str:random
  • El payload es una cadena de caracteres codificada en base64

Ejemplo

El payload de la base de datos:

YTozOntzOjY6Il90b2tlbiI7czo0MDoiamtYNUVmUXZTNVc4bnFtVGt3RWk4d3g5QmM0RFZ3aTlDTmdRWlJOSCI7czo5OiJfcHJldmlvdXMiO2E6MTp7czozOiJ1cmwiO3M6MzA6Imh0dHA6Ly9sb2NhbGhvc3Q6ODg4OC9hcGkvY3NyZiI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fX0=
Enter fullscreen mode Exit fullscreen mode

Decodificado da:

a:3:{s:6:"_token";s:40:"jkX5EfQvS5W8nqmTkwEi8wx9Bc4DVwi9CNgQZRNH";s:9:"_previous";a:1:{s:3:"url";s:30:"http://localhost:8888/api/csrf";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}"
Enter fullscreen mode Exit fullscreen mode

Esto es el resultado de serializar con php el array de atributos de la sesión.

  • jkX5EfQvS5W8nqmTkwEi8wx9Bc4DVwi9CNgQZRNH es el token csrf actual.

La cookie de sesión

Al llamar al endpoint de sanctum/csrf obtenemos dos cookies: laravel_session y XSRF-TOKEN

El contenido de estas cookies está encriptado con el algoritmo AES-256-CBC. La llave es el contenido de la variable APP_KEY. (A menos que se hayan cambiado los ajustes por defecto)

Desencriptamos el contenido de la cookie de sesión con la función Crypt::decryptString.

Crypt::decryptString("eyJpdiI6Ims3SkhEdWFxWGV0WER5Tm91dG5yK0E9PSIsInZhbHVlIjoiOER5ME8xMVhMNmlBcmkwWDJieDRtZmFRT2RGT21yWFpjYm94YXV4S21KdDZvaG9xaUF2cHk5WXNadzR0RS85b1BpbFlxWXB0blB0eUczYjhDQUhkazhYM0dRRjdEQnI4TlZIak50TDFhMGlmSy9URlpkdW5zSmZRSmd3YzlscTEiLCJtYWMiOiJmZjI5MzBiMTVmYzE3MjdhOTk3ZGM3MzZhYjdlN2IxZWY5MmVlMDljMDNiYzgzZGVmNWRjZmIzMjgxMjM0ZDBmIiwidGFnIjoiIn0")
Enter fullscreen mode Exit fullscreen mode

Nos da:

204dfbcb7897b8904470ad24455c430fbee52e49|Tx8V3BmKRGrLQDONQAol5H5QsSRXYpzYvVXhyAQw

No logré identificar que significan los primeros 40 caracterés. Los últimos 40, despues del |, son el ID de la sesión (Tx8V3BmKRGrLQDONQAol5H5QsSRXYpzYvVXhyAQw), coincide con el ID en la base de datos.

Y si desencriptamos la cookie con el token csrf:

Crypt::decryptString("eyJpdiI6InEvVHVzWWpGcndLM1k1WVg4aFlvL3c9PSIsInZhbHVlIjoiUmcrOS9vNGJFZXgyUUFMOGlyTFYxU08rYUVXNmxReDI1Vm5rK1BVNkxvUVNpZXptby9xVjcyZnBFZVl1VGRTTFFiLzgwUGhwdDFKRVV2N00xQTJZa0ZLWnVoVVhtN1lvdkduamFZZU5Oc3dua1RmRk5JdEZHZnRQaHMzZGduZ0siLCJtYWMiOiJkNTFkMTMxZjUyMWU4MmYwNDczOGQyZDQ5ODJlNmU0NzBmYTA0YTg0MjM1M2E0MzQyYjY1Mzc5MGY1YjJiNWFhIiwidGFnIjoiIn0")
Enter fullscreen mode Exit fullscreen mode

Da:

c27a7fa10ed3b8fdd1c10265852343fa8e088ef8|jkX5EfQvS5W8nqmTkwEi8wx9Bc4DVwi9CNgQZRNH

Al igual que con la cookie de sesión, no pude identificar que son los primeros 40 caracteres, parece un hash de algo. Los ultimos 40 caracteres son el token csrf.

CSRF

El token encriptado se debe enviar en todas las peticiones tipo POST/PUT/DELETE. Si es un request AJAX, debe ir en un header.

El middleware VerifyCSRFToken obtiene el token del request y lo compara con el token que se encuentra en la base de datos. Si no coinciden, ocurre el error CSRF token mismatch y la petición no se continua procesando.

En mi caso, los tokens en las cookies y la base de datos coincidían. Así como los sitios permitidos para CORS. Las peticiones de preflight también regresaban los valores correctos.

¿Que pasó?

Olvidé que había actualizado las dependencias del package.json de la SPA. Uno de los paquetes que se actualizó fue axios.

Axios automáticamente agrega el token de la cookie en un header, pero en la última versión ya no lo hace.

Hay que agregar withXSRFToken = true a la configuración de la instancia de axios.

axios.create({
  withCredentials: true,
  withXSRFToken: true,
});
Enter fullscreen mode Exit fullscreen mode

Fin.

Si alguien sabe que son o como se generan los primeros 40 caracteres del payload de la cookie, agréguelo en los comentarios.

Top comments (0)