javascriptrnode.jsghost-blog

Ghost CMS API from R


I am trying to connect to a local Ghost CMS instance from R using the build-in Admin API. There is a good documentation (https://ghost.org/docs/admin-api/#token-authentication) on how to connect for various languages but unfortunately not for R. I have compiled the following code but unfortunately receive a 401 error when trying to create a test post. Any help is much appreciated.

R code:

api_admin_key <-
  "xxxxxx:yyyyyyyyyyyyyyy"

api_admin_key <- unlist(strsplit(x = api_admin_key, split = ":"))
names(api_admin_key) <- c("id", "secret")

# Prepare header and payload
iat <- as.integer(Sys.time())
header <-
  list(alg = 'HS256', typ = 'JWT', kid = api_admin_key[["id"]])

# Create the token (including decoding secret)
payload <-
  jose::jwt_claim(iat = iat,
                  exp = iat + 5 * 60,
                  aud = '/admin/')

token <-
  jose::jwt_encode_hmac(
    claim = payload,
    secret = charToRaw(api_admin_key[["secret"]]),
    size = 256,
    header = header
  )

# Make an authenticated request to create a post
url <- 'http://localhost:2368/ghost/api/admin/posts/'
headers <- c('Authorization' = paste("Ghost", token))
body <- list(posts = list(
    "title" = 'Hello World',
    "html" = "<p>My post content. Work in progress...</p>",
    "status" = "published"
  )
)

httr::POST(url,
           body = body,
           encode = "json",
           httr::add_headers(.headers = headers))

Solution

  • It looks like the problem is the secret= you are passing to jwt_encode_hmac(). The charToRaw doesn't understand hex digits. It's just using the ascii character codes. To do the translation, you'd need one of the hex_to_raw functions from this existing question. I'll use one here

    hex_to_raw <- function(x) {
      digits <- strtoi(strsplit(x, "")[[1]], base=16L)
      as.raw(bitwShiftL(digits[c(TRUE, FALSE)],4) + digits[c(FALSE, TRUE)])
    }
    

    Also, you don't need to specify the alg and typ in the header because those are added by the function, So you would build your token with

    api_admin_key <- "adam:12bd18f2cd12"
    
    api_admin_key <- unlist(strsplit(x = api_admin_key, split = ":"))
    names(api_admin_key) <- c("id", "secret")
    
    # Prepare header and payload
    iat <- as.integer(Sys.time())
    header <- list(kid = api_admin_key[["id"]])
    
    # Create the token (including decoding secret)
    payload <-
      jose::jwt_claim(iat = iat,
                      exp = iat + 5 * 60,
                      aud = '/admin/')
    
    token <-
      jose::jwt_encode_hmac(
        claim = payload,
        secret = hex_to_raw(api_admin_key[["secret"]]),
        size = 256,
        header = header
      )
    

    I tested each of the tokens using the debugger at https://jwt.io/ and they seemed equivalant. Using the debugger, the base64 encoded value for the hexvalue "12bd18f2cd12" is "Er0Y8s0S"