I have added Bearer token authorization to my Swagger (created with Plumber), using the solution here.
Now I would like to add arbitrary headers that are a part of the request. My plumber deployment is used by a React dashboard, where parsing req$HTTP_TENANT gives me the desired value. And it is this I would like to recreate in Swagger.
Consider this example:
library(plumber)
r <- plumber::pr("plumber.R") %>%
plumber::pr_set_docs("swagger") %>%
plumber::pr_set_api_spec(function(spec) {
spec$components$securitySchemes$bearerAuth$type <- "http"
spec$components$securitySchemes$bearerAuth$scheme <- "bearer"
spec$components$securitySchemes$bearerAuth$bearerFormat <- "JWT"
spec$security[[1]]$bearerAuth <- ""
spec$components$parameters$HTTP_TENANT <- "HTTP_TENANT"
spec$parameters[[1]]$HTTP_TENANT <- "Customer name"
spec
})
r %>% plumber::pr_run(port = 8000, host = '0.0.0.0')
This gives the following result:
How can arbitrary headers be requested, for example HTTP_TENANT, maybe typed below the Bearer token input? It could also be somewhere else at the top of Swagger.
How can default values be provided for the headers, e.g. Customer name, but also the Bearer token (i.e. it could be programmatically entered from R)?
In Swagger, you can add a parameter to the API endpoint and change the "in" property to "header" to request any header, such as HTTP_TENANT. For instance:
spec$paths$"/endpoint"$get$parameters[[1]]$name <- "HTTP_TENANT"
spec$paths$"/endpoint"$get$parameters[[1]]$in <- "header"
spec$paths$"/endpoint"$get$parameters[[1]]$description <- "Customer name"
Under the "parameters" section of the Swagger UI, an input field for the "HTTP_TENANT" header will be added as a result of this.
A "default" property on the parameter object can be added to headers to provide default values. For instance:
spec$paths$"/endpoint"$get$parameters[[1]]$default <- "customer1"
The default values could also be set programmatically by reading them from an external source (like a configuration file) or by using an R package that handles authentication (like httr) to handle the bearer token automatically.
Related
I'm trying to create a function that I can apply to a data frame that will query an API for each row in my data frame.
After hours of trying I have got a response back from the API after having struggled with the whole authentication piece.
The steps needed to get a response back from the API include:
Send JSON payload with client id & secret to OAuth2.0 temp token generation endpoint
Parse access token from the response
Include access token in header of request to data endpoint which requires three parameters (ICN,SFFX & ENG values) to get a valid response
I'm now trying to build a function that incorporates all of these steps above and that I can apply/map_df to a data frame which contains the three input parameters (ICN,SFFX & ENG values) needed for the API.
What I wish to know is whether I should include steps 1 & 2 above in my function or just step 3 alone. I'm assuming if I'm iterating over rows in a data frame that I do not need to apply for temp tokens each time I send an API request for data??
In my code below I have defined the function after the OAuth phase (after first POST request).
What is the best practice for creating functions with OAuth2.0, should auth be done outside of a function?
My code looks as follows:
# INSTALL PACMAN IF NOT ALREADY INSTALLED
if (!require(pacman)) install.packages("pacman")
# LOAD LIBARIES
pacman::p_load(tidyverse, httr)
# ENABLE TOKEN CACHE TO COMPLY WITH API GUIDELINES
options(httr_oauth_cache = TRUE)
# DEFINE A USER AGENT
ua <- user_agent("https://github.mycompany.com/me")
# OAUTH2.0 TEMP TOKEN GENERATION ENDPOINT
url <- "https://gateway-stage-dmz.mycompany.com/auth/oauth2/cached/token"
# PAYLOAD TO SEND WITH THE AUTH REQUEST
body <- list(
client_id = "foo",
client_secret = "bar",
grant_type = "client_credentials"
)
# ACCESS TOKEN REQUEST
token <- POST(url,
body = jsonlite::toJSON(body,
pretty = TRUE,
auto_unbox = TRUE
),
httr::add_headers(`accept` = "application/json"),
httr::content_type("application/json")
)
# CREATE A FUNCTION TO QUERY THE API
api_fun <- function(ICN, SFFX, ENG) {
# HEADERS TO BE INCLUDED IN API CALL
headers <- c("Authorization" = sprintf("Bearer %s", content(token)$access_token))
# QUERY PARAMETERS FOR API CALL
params <- list(
icn = ICN,
suffix = SFFX,
EngineCd = ENG
)
# API RESPONSE
response <- httr::POST(
url = "https://gateway-stage-dmz.mycompany.com/api/endpoint_name_here",
httr::add_headers(.headers = headers),
query = params
)
df <- content(response,
as = "text",
encoding = "UTF-8"
) %>%
jsonlite::fromJSON(.,
flatten = TRUE
) %>%
data.frame()
}
Tokens typically last for a set amount of time, the most common default being 3600 s. Either the response holding the access token or the API's docs should tell you exactly how long the access token is fresh.
Here's an example from Google's APIs for Mobile and Desktop apps:
{
"access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in": 3920,
"token_type": "Bearer",
"scope": "https://www.googleapis.com/auth/drive.metadata.readonly",
"refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}
The expires_in field represents the number of seconds until the access token expires. At that point, you can use the refresh token to get a new access token.
If you are confident that all of your calls should be completed by then, getting the access token once and using it for all the rows should work fine (i.e., you should not need to refresh the access token for each request).
If there is a chance that the process will take longer than the lifetime of the token, you will may need to refresh the token at some point. The best way to do that depends on lots of factors, so you would need to think about the best way to implement it on a row-by-row basis, which defeats a lot of the niceness of a dataframe.
I am trying to access the Open Apparel Registry api using httr.
NB: It's free to sign up (need to login + get authentication code on profile page).
But you can see the swagger api docs here: https://openapparel.org/api/docs/#!/facilities/facilities_list
Here is how you authorize on web version:
oar_root_api <- "https://openapparel.org/api/facilities/"
oar_token <- XXX
oar_api_facilities_GET <- httr::GET(url = oar_root_api,
add_headers(
`Authorization` = oar_token),
verbose()
)
The code I receive back is 401 so something is wrong with my authorization, but I've tried so many ways. I can't figure out how to specify this correctly.
Sorry to hear you've been having difficulties. This additional documentation may help: https://docs.google.com/document/d/1ZKCN84Eu9WDAXUokojOw7Dcg5TAJw0vKnVk7RPrTPZ0/edit?usp=sharing
We tend to find that users need to add the "Token" prefix (see page 3), which I appreciate isn't standard practice - this is something we intend to change!
Let us know how you get on.
OAR
The Open Apparel Registry (OAR) uses Django REST Framework to provide API enpoints. The TokenAuthentication class requires that the Authorization header value have a "Token " prefix. From the documentation
For clients to authenticate, the token key should be included in the Authorization HTTP header. The key should be prefixed by the string literal "Token", with whitespace separating the two strings. For example:
Authorization: Token 9944b09.....
I am not familiar with R, but I searched for string concatenation and it looks like the paste function will build the header value that you need.
oar_root_api <- "https://openapparel.org/api/facilities/"
oar_token <- XXX
oar_api_facilities_GET <- httr::GET(url = oar_root_api,
add_headers(
`Authorization` = paste("Token ", oar_token)),
verbose()
)
I want to consume a REST API and have to perform the following steps for it.
I have to get a Token using my UserName and Password
(What I have accomplished successfully and the token is stored in a variable)
I have to use this Token to get data from the API and here I stuck.
I have tried
req_token <- THE TOKEN I HAVE RECIEVED ALREADY
url <- 'https://myService.com/web/api/datasources/{identifier}/data'
mydata <- GET(url, config = add_headers(paste0("Basic ", req_token)))
The identifier is there to specify a datasource within many, so in my case I had to replace it with EdQVFcgRGF0 (sort like). So I end up with the url
https://myService.com/web/api/datasources/{EdQVFcgRGF0}/data
All specification I got from the provider was
/datasources/{identifier]/data (GET)
● get data for one datasource (full data)
I tried consume the api with vb.net first and sending the token in the header works
request.AddHeader("Authorization", "Basic " + _token)
Now I get an 401 Unauthorized using R and do not know, what is wrong, anyone who can help me out?
Depending on the API configuration, I think you'll add it in where there's the curly brackets for {identifier} in the URL.
req_token <- THE TOKEN I HAVE RECIEVED ALREADY
url <- paste('https://myService.com/web/api/datasources/', req_token, '/data', sep='')
That's how some API's do it. Which means your headers might not look like this any more.
mydata <- GET(url, config = add_headers(paste0("Basic ", req_token)))
They probably just won't be there any more. So like :
mydata <- GET(url)
If the token is required in the headers it might look more like this:
mydata <- GET(url, config = add_headers("Basic " = req_token))
But I doubt the token will be both in the URL and in the header. You'll have to find out which is required from the docs.
Edit
I believe your headers should look like this:
mydata <- GET(url, config = add_headers("Authorization " = paste( "Basic", req_token, sep = ' ' ))
I'm new to BW6 (v.6.3.1) and I'm playing around with it's REST features.
Currently I'm building a very simple echo service to figure out how it processes parameters etc.
One of the things I've noticed is that I can specify multiple Reply Client Formats, namely XML & JSON. But I can't find how I can specify what output to use in the actual reply.
Currently I've setup the following Resource Service Path:
/echo/{param}.{format}
I want to use the format parameter to drive the output I'll be getting. So
/echo/printme.xml
would result in
<messageBody>printme</messageBody> (or something to that extent)
And
/echo/printme.json
would result in
printme
I've tried several approaches, splitting the flow based on the contents of "format" but to no avail. If I have JSON checked as the Reply Client Format it will reply with JSON. Only if XML is the only Reply Client Format checked the reply will output XML. BW handles the rendering of either JSON or XML transparently.
So, how can I tell BW to output in either XML or JSON when both are selected as a Reply Client Format?
Edit:
Using the swagger UI I figured out how I can drive this behavior. By specifying the following header:
curl -X GET --header "Accept: application/xml" "http://localhost:8080/echo"
As per the documentation
Select the Invoke REST API pallette, you can choose the type(either request or response) as shown below:
If you click on it, there are three options JSON, XML and Custom. If you want to choose other than json and xml then choose Custom.
Custom(For RequestType) : Custom: to override the Content-Type value in the Input tab, select CUSTOM and provide the value in the Input tab.
Custom(For ResponseType): to override the Accept header value in the Input tab, select CUSTOM and provide the value in the Input tab.
Below is the Input tab where you can override when type is Custom:
I have a shiny app (using shiny dashboard).
I am trying to pass in parameters on the url, use these to produce some data and then return it to the calling application. I put this in my server:
observe({
#all url params come in via the 'url_search' variable.
query <- parseQueryString(session$clientData$url_search)
action <- query[['action']]
if (!is.null(action)) {
#handle all supported request here
if(action == 'blah') {
#... do somework here to create my dataframe
shiny:::httpResponse(status=200, content_type="text/html; charset=UTF-8", mydataframe)
} else {
#... ignore unrecognized request
}
}
})
This works, but not in the intended way:
The request is processed correctly, but the first thing that happens is the entire shiny app is rendered.
Then, later on, I see a 'POST' request, with my original url as a referrer, and this posts the needed data as JSON data, but I don't know where it goes.
What do I need so that I can send my url (with params) to my shiny app, and then have it bypass its usual interactive mode and just return raw json data immediately without rendering html? Is it because I am making the request via a browser? Should I make the same url request programmatically in R?
update: I tried using fromJSON('...') on my url doesn't work, either. I get:
Error in fromJSON(content, handler, default.size, depth, allowComments, :
invalid JSON input
I don't believe you can do that using just Shiny. There are, however, some options to have R behave like an API and return JSON responses. See: http://plumber.trestletech.com/