Exchanging and storing tokens

The token you provide to the user needs to be stored on their machine so that they can later send it back in the request to your plumber API. Where and how the token is stored depends on your use case and what the service making the requests to your API looks like.

Exchanging tokens

Issueing the token

When you first issue a token to the user - i.e. in the authentication endpoint of our examples - , you have two options how you want to return it to them: send it as part of the HTTP response or set a token in the browser of the user.

Return in HTTP response

Return the token in the HTTP response by including the appropriate return statement at the end of your authentication endpoint.

pr$handle("POST", "/authentication", function (req, res, user = NULL, password = NULL) {

  # ... 
  # CODE HERE
  # ...

  # return jwt as response
  return(jwt = jwt)
}, preempt = c("sealr-jwt"))

This is the most flexible way as it allows the user to handle the token according to their needs. They could...

Cookie

HTTP Authorization header

Interactive R session

If your users simply use the token to make requests from an R script, e.g. by executing an .R file or generating an R markdown file, they should store their token in a secure way.

Some resources to get started: - https://db.rstudio.com/best-practices/managing-credentials/

Another application

Another service

Web application

Finally, you could have a "typical" web frontend-backend infrastructure where you want to use plumber to serve data to a frontend that your user can visit in their Internet browser.

Typically, in web development, there are two approaches to storing user data.

Local Storage

Cookie

Instead of requiring the user to send the JWT back in the HTTP Authorization header, you can also use a cookie in the browser of the API user to store the JWT. This way, the user does not have to remember to send the JWT in the Authorization header.

knitr::opts_chunk$set(echo = TRUE)

Instead of requiring the user to send the JWT back in the HTTP Authorization header, you can also use a cookie in the browser of the API user to store the JWT. This way, the user does not have to remember to send the JWT in the Authorization header.

Cookie example

In this example, we use an encrypted cookie to store the JWT. You can find the full code for this example at the end of this page. In order to do this, we register an encrypted session cookie (see plumber docs):

# define a new plumber router
pr <- plumber::plumber$new()
# register cookie
pr$registerHooks(plumber::sessionCookie(key = "EPCGoaMO9dIxIEPoOjOL4sjL4U6w0GQ5", name = "token"))

In the authentication route, we then can set the JWT in the cookie. Note that we do not return the JWT to the user because the JWT will be transferred in the cookie instead.

pr$handle("POST", "/authentication", function (req, res, user = NULL, password = NULL) {
  # ...
  # ...
  # set cookie
  req$session$token <- jwt
  return()
})

The filter looks the same as in the simple example except that we have to change the token_location parameter to "cookie" instead of "header".

# integrate the jwt strategy in a filter
pr$filter("sealr-jwt", function (req, res) {
  # simply call the strategy and forward the request and response
  # please change the secret
  sealr::authenticate(req = req, res = res, is_authed_fun = sealr::is_authed_jwt,
                      token_location = "cookie", secret = secret)
})

:warning: Please change the secret to a super secure secret of your choice. Please notice that you have to preempt = c("sealr-jwt") to routes that should not be protected.

Run the example

Copy the code from below in a new R file and save it under jwt_cookie_example.R. In the R console, run:

plumber::plumb("jwt_cookie_example.R")

In order to run this example, you need the following packages installed:

Get authentication

Using curl, Postman or a similar tool for sending HTTP requests, send a POST request with the details of one of the two users that are in the API's "database" (in this simplified example, a data frame).

users <- data.frame(id       = integer(),
                    name     = character(),
                    password = character(),
                    stringsAsFactors = FALSE)

# create test user
users <- rbind(users, data.frame(id       = 1,
                                 user     = "jane@example.com",
                                 password = bcrypt::hashpw("12345"),
                                 stringsAsFactors = FALSE))
users <- rbind(users, data.frame(id       = 2,
                                 user     = "bob@example.com",
                                 password = bcrypt::hashpw("45678"),
                                 stringsAsFactors = FALSE))
kableExtra::kable(users, format = "markdown")

For example, in curl:

Note: You might get different encrypted JWTs in your cookies_jane.txt as jose::jwt_encode_hmac automatically adds the time when the JWT was issued as a claim (iat claim).

curl  --cookie-jar cookies_jane.txt --data '{"user": "jane@example.com", "password": "12345"}' localhost:9090/authentication

The --cookie-jar argument will store the cookie in a plain-text file in your current directory called cookies_jane.txt.

If you inspect this file (e.g. using cat cookies_jane.txt), you should see something like this:

# Netscape HTTP Cookie File
# https://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

localhost   FALSE   /   FALSE   0   token   R/cOKStnaCrwojcbL4Uk1602scv7ln9PClDYO2LlfHd0zYtd+vYrGB6oTX7wvpp5hmipFxqnQ0FDwEdz1H7IjCr6KCxxUHnpWu8r0IjCA4zLHbJz/i5npGe16Ei+OhbOGbluT/An+0GnfzXsna4q4vHoB2P+GkUPZL3Xe7tZxIX+UHSk005tX89NcSLpVy6J

The token is the gibberish part at the end. It is indeed the JWT but it is encrypted and hence does not look like a JWT.

Trying to get a token cookie for a user that is not in the database of course fails:

curl --cookie-jar cookies_drake.txt --data '{"user": "drake@example.com", "password": "10111213"}' localhost:9090/authentication
{"status":["Failed."],"code":[401],"message":["User or password wrong."]}

Route without required authentication

Everyone can access the / route because it does not require authentication - the sealr-jwt filter is preempted for this route:

curl localhost:9090
["Access to route without authentication was successful."]

Route with required authentication

Trying to access the /secret route without a valid cookie fails because it goes through the sealr-jwt filter where the sealr::jwt function will check for the correct authentication details - in this case a valid JWT in token cookie.

curl localhost:9090/secret
{"status":["Failed."],"code":[401],"message":["Authentication required."]}

But if you add the cookies for Jane to the request, authentication is successful.

curl --cookie cookies_jane.txt localhost:9090/secret
["Access to route requiring authentication was successful."]

Code {code}




jandix/sealr documentation built on Oct. 3, 2021, 1:16 p.m.