CTEC2712 Web application development

Cookies and flash messages

Managing context on the browser.

Contents

  1. HTTP is stateless
  2. cookies
  3. Reading cookie data
  4. Deleting cookies
  5. security
  6. Flash messages
  7. Set flash message on redirect
  8. Display flash message when rendering

HTTP is stateless

Each request is processed independently by the server with no memory of previous requests, so clients must resend all context every time.

A diagram showing HTTP messages travelling between web browser and web server

Our web applications need continuity between requests, they need some state to be stored in the browser.

1

cookies

Cookies are a mechanism for passing name, value pairs to the browser via a header.

just some cookies, chocolate chip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { setCookie } from "@std/http/cookie";

function someRequestHandler(request) {

    // We need to create some headers for the response
    const headers = new Headers({
        "content-type": "text/html"
    });    

    // We can set a cookie on the headers
    // The setCookie function does all the work.
    setCookie(headers, {
        name: "myCookie",
        value: "chocolateChip",
        path: "/"
    });

    // We can then just include it with the response.
    const html = "<p>This response sets a cookie.</p>";
    return new Response(html, { headers });
}

The browser will store the data and send it back in every request under the given path.

2

Reading cookie data

A cookie header will be included with every subsequent request.

just some cookies, chocolate chip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { getCookies } from "@std/http/cookie";

function someRequestHandler({request}) {

    // We can read the cookie header from the request
    const cookies = getCookies(request.headers);

    // The getCookies function turns it into a nice object
    console.log(cookies);   // { myCookie: "chocolateChip" }    

    // Extracting individual cookies is easy
    const myCookie = cookies.myCookie;

    // We can include the data in the response or do anything we want with it.
    const html = `<p>Your cookie is ${myCookie}</p>`;
    const headers = { "content-type": "text/html" };
    return new Response(html, {headers})
}

This allows the browser to maintain state.

3

Deleting cookies

We can delete a cookie from the browser by sending another header.

just some cookies, chocolate chip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { deleteCookie } from "@std/http/cookie";

function someRequestHandler({request}) {

    // We need to create some headers for the response
    const headers = new Headers({
        "content-type": "text/html"
    });

    // The deleteCookie function adds a header with the necessary instructions
    deleteCookie(headers, "myCookie", { path: "/" });

    // The browser will respond by deleting the cookie.
    const html = `<p>This response deletes a cookie.</p>`;
    const headers = { "content-type": "text/html" };
    return new Response(html, {headers})
}

In this way, our server can control the browser cookie state. Although users can clear their cookies.

4

security

To harden up our security, we should set some options.

just some cookies, chocolate chip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { setCookie } from "@std/http/cookie";

const headers = new Headers();

setCookie(headers, {
    name: "myCookie",
    value: "chocolateChip",
    path: "/",
    httpOnly: true,     // prevents JS access (XSS mitigation)
    secure: true,       // HTTPS only (but not suitable for us)
    sameSite: "Strict", // CSRF protection
    maxAge: 3600
});

In development, we are serving over plain HTTP so we can’t use secure cookies.

5

Flash messages

Cookies are somewhat limited because they have a specific format.

just some cookies, chocolate chip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { deleteCookie, getCookies, setCookie } from "@std/http/cookie";
import { encodeBase64Url, decodeBase64Url } from "@std/encoding";

export function setFlash(headers, message) { 
    setCookie(headers, {
        name: "flash",
        value: encodeBase64Url(message),
        path: "/",
        httpOnly: true,
        secure: true,       // false for development  
        sameSite: "Strict",
        maxAge: 3600
    });
}

export function getFlash(requestHeaders, responseHeaders) { 
    const { flash } = getCookies(requestHeaders);
    if (flash) {
        deleteCookie(responseHeaders, "flash", { path: "/" });
        return new TextDecoder().decode(decodeBase64Url(flash));       
    }
}

Encoding data allows us to add arbitrary messages into cookies.

6

Set flash message on redirect

When we redirect, we can optionally pass a flash message to the browser

just some cookies, chocolate chip

1
2
3
4
5
6
7
import { setFlash } from "./flash.js";

export default function redirect(headers, location, flash) { 
    if(flash) setFlash(headers, flash);
    headers.set('location', location);
    return new Response(null, { headers, status: 303 });
}

The message will be passed back as a cookie in the next response.

7

Display flash message when rendering

When we render an HTML response, we can include (and delete) the flash message.

just some cookies, chocolate chip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { escape } from "@std/html/entities";
import { getFlash } from "./flash.js";

export default function render(viewFn, data, request, status=200) { 
    const headers = new Headers({"content-type": "text/html"});
    const content = viewFn(data);

    const msg = getFlash(request.headers, headers);
    const flash = `<aside id="flash"><p>${escape(msg)}</p></aside>`;

    const html = `
        <!DOCTYPE html>
        <html lang="en">
            <head>
                <title>My web application</title>
            </head>
            <body>
                <header><h1>My web application</h1></header>
                <main>
                    ${msg ? flash : ""}
                    ${content}
                </main>
                <footer><p>&copy; application developers</p></footer>
            </body>
        </html>`
    return new Response(html, { headers, status });
}

If there is no message, nothing is included.

8

Cookies and flash messages

If you have any questions, now is a good time to ask.

Thanks for listening