It can be useful to track a user as they interact with the API. One common way to do this is by setting a cookie on the user’s browser/client so that we can identify separate requests as all belonging to the same user.
You can set a cookie by using the
setCookie() method on the response object given to a filter/endpoint. The value of the cookie (the second argument) in this method will be stored as a cookie after passing it through
as.character, but not other serialization will take place. So numbers or character strings should be just fine, but a complex list will likely not get stored in the way you’re hoping for.
Here’s an example of using
This endpoint would first check to see if a
visitcounter object had already been set on
req$cookies. It would use that value, if found, to set the counter; otherwise it would start the counter at 0. Then it increments the counter and uses
res$setCookie to set the new value for the
You’ll find that if you access this endpoint from a browser, it will set a ‘visitcounter’ cookie that increments every time you hit the endpoint, as the example below shows.
Encrypted Session Cookies
Setting individual cookies yourself is a reasonable way to manage your user’s sessions. However, plumber also includes a mechanism to store a list of data and (optionally) even encrypt that list. To use this feature, you must explicitly add it to your router after constructing it. For example, you could run the following sequence of commands to create a router that supports encrypted session cookies.
You’ll notice the above example is using the
sessionCookie middleware that comes with plumber. By adding this as a global processor on your router, you’ll ensure that the
req$session object is made available on incoming requests and is persisted to the cookie named
cookieName when the response is ready to be sent to the user. In this example, the key used to encrypt the data is
"secret", which is obviously a very weak secret key.
res$setHeader(), the values attached to
req$session are serialized via jsonlite; so you’re free to use more complex data structures like lists in your session. Also unlike
req$session encrypts the data using the secret key you provide as the first argument to the
To recreate our example above using
Again, you would need to add the
sessionCookie() middleware as a global processor on your router before this code would work.
You’ll find that the behavior of the endpoint is the same as the unencrypted version above, but this time the cookie value stored in the user’s browser is encrypted.
There are two approaches to sessions on servers: you can either store all of the state in a cookie, or store a unique identifier in a cookie and keep the state on the server. There are pros and cons to both approaches.
Storing all of the state in the cookie simplifies your infrastructure. You don’t need a database or a centralized store on the server side to keep all of your active session data which is one less thing for you to maintain. However, there’s a limit to the size of a cookie (4kB for most browsers), so you will need to make sure that the data you need to store for your users will always fit in that size (plus some wiggle room for the overhead encryption may incur).
Keeping your state on the server allows you to keep much more than 4kB of information per user, as you can keep as much information as you want on your filesystem or in your own database. Then all you need to have the user store is a unique identifier that allows you to map the user back to the state you’ve stored on the server. In this case, you should use a cryptographically random (i.e. not
rnorm) generator for your session IDs so that users won’t be able to guess the next session ID that you’re going to assign to the next user that comes along.
Bear in mind that cookies can trivially be forged. A user can claim that the value of some cookie you assign is whatever value they choose to send to your server. So it’s important to leverage encryption if you’re sending any data that a malicious user might be able to tamper to cause harm. However, once the cookie is encrypted (with an appropriately long/complex key), it’s much more reasonable to store that information in a cookie and expect that an attacker would not be able to alter anything in the message that you’re sending.
A full discussion of CORS is outside of the scope for this article, but be aware that there are some complexities involved in using cookies from a domain name other than the one on which your site is hosted. So if you intend to use an API hosted on one domain from your website hosted on another, you will need to read up on the details of CORS and ensure that your plumber API and your clients are all properly configured to comply with the various restrictions that will come into play. See this article for a brief presentation of the issue.