En este post aprenderemos qué son y como funcionan las cookies para crear una base de conocimiento que nos sirva para tutoriales posteriores sobre la creación y lectura de cookies en dos contextos específicos: server-side con PHP y client-side con JavaScript. ¿Preparado? Empezamos.

¿Qué son las cookies?

Las cookies, de nombre más exacto HTTP cookies, es una tecnología que en su día inventó el navegador Netscape (descanse en paz), actualmente definida en el estándar RFC 6265, y que consiste básicamente en información enviada o recibida en las cabeceras HTTP y que queda almacenada localmente client-side durante un tiempo determinado. En otras palabras, es información que queda almacenada en el dispositivo del usuario y que se envía hacia y desde el servidor web en las cabeceras HTTP.

Cuándo un usuario solicita una página web (o cualquier otro recurso), el servidor envía el documento, cierra la conexión y se olvida del usuario. Si el mismo usuario vuelve a solicitar la misma u otra página al servidor, será tratado como si fuera la primera solicitud que realiza. Esta situación puede suponer un problema en muchas situaciones y las cookies son una técnica que permite solucionarlo (de las muchas técnicas que hay).

Con las cookies, el servidor puede enviar información al usuario en las cabeceras HTTP de respuesta y esta información queda almacenada en el dispositivo del usuario. En la siguiente solicitud que realice el usuario la cookie es enviada de vuelta al servidor en las cabeceras HTTP de solicitud. En el servidor podemos leer esta información y así “recordar” al usuario e información asociada a él.

¿Cómo funcionan?

Una cookie consiste en una cadena de texto (string) con varios pares key=value cada uno separado por ;:

<nombre>=<valor>; expires=<fecha>; max-age=<segundos>; path=<ruta>; domain=<dominio>; secure; httponly;

Desde el servidor, las cookies son creadas mediante la cabecera de respuesta HTTP Set-Cookie:

Set-Cookie: <nombre>=<valor>; expires=<fecha>; max-age=<segundos>; path=<ruta>; domain=<dominio>; secure; httponly;

Un respuesta HTTP puede contener múltiples cabeceras Set-Cookie, una por cada cookie. Por ejemplo, una cabecera de respuesta con creación de dos cookies podría ser:

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: colorPreference=blue
Set-Cookie: sessionToken=48745487; Expires=Thu, 01 Jan 2031 19:22:10 GMT

Una vez que la cookie ha sido creada, cada nueva solicitud que realice el usuario enviará la cookie en la cabecera de solicitud HTTP Cookie. En esta cabecera sólo se envían las cookies que sean válidas según los parámetros establecidos al crearla (expires, path, etc) y sólo se envía el par <nombre>=<valor>:

Cookie: <nombre>=<valor>

Cuándo se envían varias cookies, se envían todas separadas por ; en una única cabecera Cookie. Por ejemplo, una cabecera HTTP de solicitud que envía las dos cookies anteriores podría ser:

GET /ejemplo.html HTTP/1.1
Host: www.mysite.com
Cookie: colorPreference=blue; sessionToken=48745487

Limitaciones

En total, cada cookie puede tener un tamaño máximo de 4.096 bytes, se permiten hasta 50 cookies por dominio y 3000 cookies por navegador/dispositivo.

Parámetros

Veamos cada uno de los parámetros de una cookie en más detalle.

<nombre>=<valor>
<nombre> es el nombre (key) que identifica a la cookie y <valor> es su valor. El <nombre> es obligatorio para poder crear una cookie mientras que el valor es opcional (más adelante veremos como las cookies sin valor o con valor vacío son tratadas de forma diferente en PHP y JavaScript).
Como <nombre> y <valor> se puede utilizar cualquier valor arbitrario que deseemos, sólo hay que tener en cuenta que:
<nombre>: es definido como un token y como tal solo puede contenter caracteres alfanuméricos más !#$%&'*+-.^_`|~. No puede contenter ningún espacio, coma o punto y coma. Tampoco están permitidos los caracteres de control (\x00, \x1F más \x7F) y no se debería utilziar el signo = que es utilizado como separador.
<valor> puede contenter cualquier valor alfanumérico excepto espacios, comas, punto y coma, caracteres de control, barra invertida y comillas dobles. En caso de que sea necesario el uso de estos caracteres, el valor de la cookie deberá ser codificado (reemplazar esos caracteres por su código ASCII); en JavaScript lo podemos hacer con encodeURIComponent(); en PHP se podría hacer con urlenconde(); al leer el valor habría que descodificarlo con decodeURIComponent() o urldecode().
expires=<fecha> y max-age=<segundos>
Opcional. Ambos parámetros especifican el tiempo de validez de la cookie. expires establece una fecha, que ha de estar en formato GMT/UTC (por ejemplo, Mon, 03 Jul 2006 21:44:38 UTC). max-age establece una duración máxima en segundos (si segundos). Si se especifican ambos, max-age toma preferencia (max-age no es soportado por IE 8 e inferior).
Si no se específica ninguno de los dos, la cookie sólo es válida para la sesión actual, lo que se conoce como session cookie (por ejemplo, hasta que el usuario cierre el navegador). Si max-age es cero la cookie se elimina, al igual que si expires es una fecha pasada.
path=<ruta>
Opcional. Establece la ruta para la cuál la cookie es válida. Algo así como los “directorios” o “secciones” de la web. Por defecto, si no se especifica ningún valor, una cookie sólo es válida para el path actual (el directorio que contiene la página actual). Por ejemplo, si la cookie se establece en “https://ejemplo.com/noticias/pagina.html”, la cookie será válida para cualquier otra página del directorio “https://ejemplo.com/noticias/”. Utilizando el parámetro path podemos establecer un directorio diferente. Por ejemplo, “/” sería para todos los directorios del dominio, incluyendo el directorio raíz; “/blog” sería sólo para páginas bajo el directorio “http://dominio.com/blog/”.
domain=<dominio>
Opcional. Por defecto, las cookies son válidas sólo para el subdominio actual en el que se crea la cookie (ten en cuenta que www.ejemplo.com se considera el subdominio www).
Esto quiere decir que si el atributo domain está vacío y estamos en ejemplo.com, la cookie es válida sólo para ejemplo.com, si estamos en www.ejemplo.com, la cookie será válida sólo para www.ejemplo.com, si estamos en sub.ejemplo.com, la cookie es válida sólo para sub.ejemplo.com, o sí estamos en foo.sub.ejemplo.com, la cookie es válida sólo para foo.sub.ejemplo.com.
Pero si el atributo domain no está vació, podemos especificar otros subdominios para los que la cookie es válida. Por ejemplo, domain=sub.ejemplo.com creará una cookie válida sólo para sub.dominio.com; si estamos en otro subdominio, por ejemplo foo.ejemplo.com, e intentamos leer esa cookie, no podremos, ya que sólo era válida para sub.ejemplo.com.
También podemos hacer que una cookie sea válida para todo el dominio y sus subdominios estableciendo domain=.ejemplo.com (nota el punto delante del nombre del dominio). La especificación RFC 6265 antes mencionada, dice que el primer punto será ignorado, es decir, domain=.ejemplo.com y domain=ejemplo.com tienen el mismo efecto y crean una cookie válida para cualquier subdominio.
Lo anterior tiene una importante implicación. Si estamos en ejemplo.com y queremos una cookie válida sólo para ejemplo.com, la única opción es dejar el atributo domain vacío ya que si establecemos domain=ejemplo.com hacemos la cookie válida para todos los subdominios, no sólo para ejemplo.com.
Sin embargo, la especificación RFC 2965 sobre la cabecera Set-Cookie2, requiere el punto precedente para crear una cookie válida para todos los subdominos. Además, en la especificación RFC 2109 ya obsoleta, domain=.ejemplo.com y domain=ejemplo.com no eran tratados como lo mismo.
Por estos motivos, en mi opinión, es recomendable utilizar siempre el punto precedente si queremos crear una cookie válida para todos los subdominios ya que garantiza el mismo comportamiento en implementaciones antiguas que puedan serguir especificaciones obsoletas pero también en implementaciones modernas.
Por motivos de seguridad, no se permite crear cookies para dominios diferentes al que crea la cookie (same-origin policy).
secure
Opcional. Este parámetro no tiene ningún valor. Si está presente la cookie sólo es válida para conexiones encriptadas (por ejemplo mediante protocolo HTTPS).
HttpOnly
Opcional. Este parámetro no tiene ningún valor. Si está presente, la cookie solo es accesible mediante protocolo HTTP (o HTTPS). Estas cookies no pueden ser leídas ni creadas mediante otros protocolos y APIs, por ejemplo, JavaScript.

Third-party cookies

Es importante aclarar que las cookies son enviadas y recibidas con cualquier solicitud, no necesariamente una página web. Por ejemplo, al solicitar una imagen o un script pueden enviarse cookies. Esto hace que se pueda estar en una determina página que carga un recurso desde otro dominio y que haya cookies de ambos dominios. La cookie del otro dominio se conoce como third-party cookie y, como se ha creado al solicitar un recurso a ese dominio, no significa que se haya saltado la regla del same-origin.

¿Cómo utilizar las cookies?

Hemos visto que son las cookies, como funcionan y en que consisten. Cómo utilizarlas depende del contexto. A continuación veremos dos casos concretos: un caso server-side con PHP y un caso client-side con JavaScript:

  • isaac

    Hola enhorabuena por el post.

    No llego a entender la definición del atributo domain de
    docuemnt.cookie que se explica aquí, y tampoco veo gran cosa googleando sobre
    este atributo domain, si puedes intentar explicármelo algo mejor.

    • isaac

      Entiendo perfectamente esto:

      Por defecto, las cookies son válidas
      sólo para el subdominio actual en el que se crea la cookie

      Y esto:

      Para que la cookie sera válida para cualquier subdominio,
      hay que establecer domain=.miweb.com
      (nota el punto delante del nombre del dominio). Por motivos de seguridad, los
      navegadores no permiten crear cookies para dominios diferentes al que crea la
      cookie (same-origin policy).

      Pero cuando comentas lo siguiente es donde ya me lio porque
      no se porque haces referencia a esto y para que:

      (ten en cuenta que http://www.miweb.com se considera el subdominio
      www y que miweb.com es un alias del subdominio www)

    • Buenos días Isaac.

      Quiero decir simplemente que una cookie establecida para www.ejemplo.com no es válida para ejemplo.com porque técnicamente www.ejemplo.com es un subdominio y ejemplo.com es otro.

      Y lo de alias, no se muy bien como explicarlo. A ver si lo consigo.

      En realidad, el servidor trata las solicitudes a ejemplo.com como solicitudes a www.ejemplo.com, uno es un “alias” del otro. Esto es así en la mayoría de servidores, antes ambas solicitudes se trataban por separado pero vamos, no creo que hoy en día existan serivdores en los que no funcionen ambas versiones del dominio como la misma.

      Entonces, si tu pones una cookie para www.ejemplo.com y un usuario entra por ejemplo.com, va a recibir correctamente el documento porque el servidor trata la solicitud así, pero la cookie no va a ser válida. Por eso es tan importante elegir una versión “canónica” del dominio y que se redirijan a los usuarios a la versión correcta. Yo he elegido la versión sin “www” y si entras a mi blog con “www” serás redirigido a la versión sin “www”.

      Por ejemplo, en este hilo en los foros de soporte de WordPress puedes ver como tener mezcladas ambas versiones, con y sin “www”, produce problemas en las cookies.

    • isaac

      Hola, he estado leyendo e informándome en muchos sitios
      sobre este atributo porque la verdad me ha sido muy confuso y en algunas cosas
      de la explicación que me distes tampoco estaba muy de acuerdo y no llegaba a
      terminar de comprender bien el atributo.

      Por ejemplo me comentastes en tu respuesta
      que “técnicamente http://www.ejemplo.com
      es un subdominio y ejemplo.com es otro, cuando ejemplo.com es un dominio. También
      he estado mirando y la especificación ha cambiado y ahora los puntos antes del
      dominio son ignorados.

      https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie

      Así y todo como quería comprender bien este atributo he
      hecho una definición sobre él lo mejor posible
      y si me permites cogiendo algunos detalles tuyos:

      El atributo domain indica a que dominio y solo a ese (por motivos de seguridad,
      los navegadores no permiten crear cookies para dominios diferentes al que crea
      la cookie (same-origin policy), es válida la cookie.

      Deficinion:

      domain=domain (por ejemplo, ” example.com ” o ” subdomain.example.com ‘). Si
      este atributo no se define de forma explícita se utiliza como valor predeterminado el dominio de la página
      que envía la cookie (pero sin incluir subdominios). Contraria a las especificaciones
      anteriores, los puntos al principio en los nombres de dominio son ignorados. Si
      se especifica un dominio, los subdominios siempre se incluyen.

      https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie

      Cuando se envía una cookie integrada en un encabezado HTTP,
      se envía algo así:

      Set-cookie: libropreferido=ahora+aprendo+yo; path=/, domain= example.es; expires=Friday, 24-Dec-2016 12:00:00
      UTC

      Llaman la atención algunos detalles:

      Al poner como valor en el atributo domain el nombre del
      dominio los subdominios también serán incluidos, como por ejemplo: http://www.example.es (ten en cuenta que http://www.example.es se considera el subdominio www),
      misubdominio.example.es…etc. Volvemos
      a remarcar que para que la cookie sea
      válida para cualquier subdominio, con poner solo el nombre del dominio se incluirán
      siempre los subdominios, y que por
      motivos de seguridad los navegadores no permiten crear cookies para dominios
      diferentes al que crea la cookie.

      ¿Porque si creas una cookie en example.es
      no sería válida en http://www.example.es ?

      Respuesta:

      Porque, si tienes un dominio example.es, miweb.com…, entonces todo lo que está
      antes del dominio es un sub-dominio: y www se considera un subdominio, ftp,
      mail, webmail, etc.

      Solución:

      Poner como hemos
      dicho en el atributo domain el nombre
      del dominio y ya que con ello tendremos los subdominios también incluidos.

      Otra opción que se
      podría utilizar para la versión con “www” y en caso de que no se especifique en
      el atributo los subdominios, seria
      elegir una versión “canónica” del dominio y que se redirijan a los
      usuarios a la versión correcta. Yo he elegido la versión sin “www” y
      si entras a mi blog con “www” serás redirigido a la versión sin
      “www”. Aparte, obtendrás muchos beneficios eligiendo una versión
      “canónica” del dominio, para seo, evitar penalización de google…etc.

    • Bueno, lo de que www.ejemplo.com es un subdomino y ejemplo.com es otro era sólo una forma de hablar. Está claro que ejemplo.com es el dominio y todo lo demás que pongas delante son subdominios.

      Lo que tienes que tener claro que si pones una cookie para www.ejemplo.com, no va a ser válida para ejemplo.com. Pero si pones una cookie para .ejemplo.com (nota el punto delate), va a ser válida para ejemplo.com, www.ejemplo.com y cualquier otro sub.ejemplo.com.

      Y he vuelto a poner .ejemplo.com con el punto delante porque la referencia que mencionas sobre los puntos precedentes al domino, al contrario de lo que dices, no es una especificación, es la forma en la que funciona un motor de renderizado JavaScript específico que se llama Gecko y que es desarrollado por Mozilla. Otros motores de renderizado puede que no ignoren esos puntos, ni siquiera Gecko 6 también lo entederá pues ignorará el punto (algo ignorado da igual que esté o no, no producirá ningún error si está, pero si está podrá ser entendido por otros motores de renderizado). Incluiré esta explicación en el post.

      Además, ten en cuenta que este post trata específicamente sobre las cabeceras HTTP Cookie, que no tiene porque coincidir con ninguna implementación de JavaScript. La referencia aplicada es, como se menciona al comienzo del post, la RFC 62665. Tengo otro post específico sobre el uso de cookies en JavaScript, por si te interesa, dónde si trato sobre document.cookie, aunque digo básicamente lo mismo sobre el atributo domain.

    • isaac

      pense que lo que te enlace de mozilla era una especificacion, porque como no pone nada alli que eso se aplica a solo gecko. crei que se aplicaba a todos los navegadores de una version para adelante.

      ahora si lo comprendo mejor entonces. lo que si me gustaria me aclararas que me quede un poco con la duda cuando dijistes esto:

      Lo que tienes que tener claro que si pones una cookie para http://www.ejemplo.com, no va a ser válida para ejemplo.com.

      me imagino y si puedes confirmamelo, que esto es asi porque en el atributo domain estas poniendo solo un subdominio y solo se aplica a ese subdominio no al dominio, si se pusiera subdominio.miweb.es pasaria lo mismo me imagino. ¿ estoy en lo cierto?

    • Exacto!!! Una cookie con domain=www.ejemplo.com es válida sólo para el subdomino “www”, y una cookie con domain=sub.ejemplo.com es válida para el subdomino “sub”; ninguna de ellas para válida para “ejemplo.com” a secas, para el dominio superior.

      Si pones la cookie para domino con el punto delante, como “.ejemplo.com”, es válida para el dominio y todos los subdominios. Lo mismo ocurre si no pones el punto, sólo “ejemplo.com”, pero sin el punto es inválido en viejas implementaciones de JavaScript (como por ejemplo Gecko < 6).

      Además, si no se específica ningún valor en el parámetro domain y estás en ejemplo.com (sin el punto delante) la cookie sólo es válida para ejemplo.com y NO para sus subdominios; pero si pones la cookie con domain=ejemplo.com (sin el punto) ya SI es válida para ejemplo.com y para todos sus subdominos, exactamente igual que si los con el punto delante.

      Esta diferencia en como se comporta la cookie con y sin valor para domain es muy importante. Iimplica que dejar el parámetro domain en blanco es la única forma de crear una cookie válida sólo para ejemplo.com y NO para los subdominios,

      Definitivamente, voy a tener que reescribir esa parte del post para dejarlo más claro. Con tus consultas me he dado cuenta de que, aún estando correcto, son demasiados detalles no explicados con la suficiente concreción.

    • isaac

      Bueno, me alegro de que hayas sacado al final provecho de mis
      consultas y surgiera una definición más detallada sobre este atributo. Esperare
      hasta que hagas las modificaciones sobre el tema del punto en los navegadores
      basados en gecko.

    • isaac

      Upss! se me paso una cosa por alto.

      Cuando comentaste esto:

      Lo mismo ocurre si no pones el punto, sólo “ejemplo.com”, pero sin el punto es inválido
      en viejas implementaciones de JavaScript (como por ejemplo Gecko 6, que ahora si
      seria valido como me dijiste, sino para cualquier motor de renderizado como
      hemos estado hablando. Como comentaste
      esto ya me lie un poco:

      “pero sin el punto es
      inválido en viejas implementaciones de JavaScript”

    • No exactamente. Si estás en ejemplo.com y no pones nada en domain, la cookie será válida sólo para ejemplo.com y no para ningún subdomino. Pero si pones domain=ejemplo.com ya es válida para ejemplo.com y para cualquier subdominio, pero sin el punto obtendrás resultados inesperados en algunas implementaciones antiguras.

      Lo que viene a decir es que, si estás en ejemplo.com, dejar el parámetro domain en blanco es la única forma de hacer una cookie válida sólo para el dominio principal y no para los subdominios.

    • isaac

      ¿Resultados inesperados en algunas implementaciones antiguas?
      ¿Por qué?

      Pues ahora me dejas con dudas porque como bien habías dicho
      lo del dominio sin puntos es válido a partir de Gecko > y puedes ver a continuación
      como se puede poner en el atributo el dominio solo y en muchos sitios más y además
      no nombran que ahora en Gecko se pueda prescindir
      del punto por lo que me imagino que habla de la antigua implementación es
      decir, me imagino que en Gecko <6 significaba algo poner el dominio solo tal
      como se puede ver aquí:

      domain=domainname – Optional. Specifies the domain of
      your site (e.g., 'example.com', '.example.com' (includes all subdomains),
      'subdomain.example.com'). If not specified, the domain of the current document
      will be used

      http://www.w3schools.com/jsref/prop_doc_cookie.asp

      https://developer.mozilla.org/es/docs/DOM/document.cookie

      Y lo del tema de dejar el parámetro en blanco eso si lo
      entiendo.

    • Pues lo que pones de w3schools y lo que you te he dicho es lo mismo. ¿Qué es lo que no entiendes de lo que te dije? Empiezo a pensar que no lees con atención.

      En w3schools dice lo mismo que yo. Si pones ‘example.com’ o ‘.example.com’ es válido para todos los subdominos, incluyendo el dominio principal. Si no pones nada es válido sólo para dónde estés.

      Precisamanete por ese comportamiento, te decía es que si estás en ‘example.com’ y quieres una cookie sólo para ‘example.com’, no puedes poner domain=example.com porque será válida para todos los subdominos, tendrás que dejarlo en blanco si quieres que sólo sea válida para el actual ‘example.com’. ¿Me pillas? La única forma de estar en example.com y poner un cookie solo para example.com es dajar el parámetro vacío.

      Y si quieres una cookie válida para todo el dominio y subdominios y no pones el punto delante, pues no se como funcionará en implementaciones antiguas de JavaScript que requerían ese punto, por eso digo lo de resultados inesperados. Yo recomiendo siempre el punto delante para estas cookies porque es entendido por todas los motores JavaScript, antiguos y nuevos, y porque está en las especificaciones.

    • isaac

      Si leo con atención, ya que suelo releer todo siempre varias veces.

      A ver, entiendo bien todo lo que me estas explicando. Lo que no entiendo tiene que ver con esto:

      “Y si quieres una cookie válida para todo el dominio y subdominios y no pones el punto delante, pues no se como funcionará en implementaciones antiguas de JavaScript que requerían ese punto, por eso digo lo de resultados inesperados.”

      En el enlace de w3schools vez que pone; e.g., ‘example.com’, ‘.example.com’ (includes all subdomains), ‘subdomain.example.com’).

      ¿Ves el example.com (sin punto delante)? ¿Estos ejemplos y explicación que pone w3schools a que corresponden, a implementaciones antiguas de JavaScript? Te digo esto, porque al ver en el ejemplo el dominio sin el punto me pienso que los ejemplo corresponden a como he dicho implementaciones antiguas de JavaScript y por eso te pregunte qué significaba poner el dominio sin el punto en Gecko 6”, lo sé porque me lo dijiste tu, entonces lo primero que pienso es que el dominio sin el punto significa algo poniéndolo en el atributo domain en implementaciones antiguas. Creía que el dominio sin el punto se aplicaba a Gecko >6 y significaba que es válido para todos los subdominos, incluyendo el dominio principal. Y al utilizarlo en Gecko <6 significaba otra cosa como por ejemplo lo que te dije, que se incluía solo el dominio es decir, pensé que tenía significados diferentes y que era valido el dominio sin el punto en Gecko<6.

      Espero haberme explicado lo mejor posible.

    • La especificación RFC 2109 (antigua) no trata igual con punto y sin punto. Si no pones el punto para una cookie que quieres para todos los subdominios, tendrás efectos inesperados si un usuario te visita en un navegador antiguo que siga esta especificación. La cookie no será para todos los subdominios. Además, la especificación dice: “An explicitly specified domain must always start with a dot.”

      La especificación RFC 6265 (actual) trata igual con punto y sin punto.

      La especificación RFC 2965 (Set-Cookie2) requiere obligatoriamente el punto para cookies para todos los subdominos.

      No sé como más explicartelo. Creo que has agotado todos mis recursos didácticos ;). Pon el punto si quieres una cookie para todos los subdominios, es la única forma que funciona igual en las tres especificaciones.

      Y si tienes dudas sobre lo que pone en w3schools o cualquier otra web que no sea una referencia, pues no sé, preguntales a ellos. Yo no puedo responderlo. Me imagino que se refirirán a la especificación RFC 6265.