Closed schuemie closed 3 years ago
There's a post on the WebAPI git that I think should be relevant here: https://github.com/OHDSI/WebAPI/issues/1473#issuecomment-600103652
Jinx!
@schuemie beat me to it. Of course :)
thank you for working on this!
Could you please confirm -without this enhancement, we cannot use ROhdsiWebApi (even with a bearer token?)
@ablack3 would you like to redo the PR based on the develop branch for v1.0.0 release?
@gowthamrao Yes!
My WebAPI instance disappeared when I updated windows so it will take me some time to get everything set up again.
Talking with Konstantin about the the best way to add this. Since we want to support all types of authentication I think it is work thinking through how this should work. In every case we need to pass a header to the http requests. Sometimes that header is a token and other times it is a cookie.
I think it would be if the user of this package did not need to manage the token or cookie.
The user authenticates by running an authenticate()
function which will allow the user to supply an auth method or guess the appropriate auth method if none is supplied. If authentication is successful an authHeader
is created and saved in a temp file location that will be deleted when the R session ends. This header then automatically gets added to every httr
call.
If the user gets a 401 error (unauthorized) on any call then a message prompting the user to call the authenticate()
function will be displayed.
Or something like that. There is this idea of a function's or package's API (i.e. how the user of the package interacts with the functions) that I think is very important to think about.
I've been looking at implementing oauth and learning more about API security in the process. A couple questions to throw out in case anyone knows more about this stuff.
OAuth2 Terminology: Protected resource = WebApi Client application = ROhdsiWebApi Resource owner = the user Authorization server = google oauth server
Should ROhdsiWebApi have its own client ID & secret or should it use the client ID & secret of the Atlas instance? It seems to me like ROhdsiWebApi should have a separate client id and secret since it is a essentially a different native application accessing WebApi. I think we would need to set up a single client id and secret for all ROhdsiWebApi installations and hard code it into the package. ROhdsiWebApi would then use these to authenticate to the authorization server to get the access token (bearer token). However one thing I'm not sure about is if WebApi should be able to verify the access token sent by ROhdsiWebApi. Would every WebApi installation with oauth enabled be able to verify access tokens created using the plan above without additional configuration?
Here is an example using Google's Identity Aware Proxy (IAP). It involves first creating a service account on Google Cloud Platform and relies on changes to ROhdsiWebApi in https://github.com/OHDSI/ROhdsiWebApi/pull/148
example_service_account.json - Created by Google for the service account
{
"type": "service_account",
"project_id": "my-project-id",
"private_key_id": "...",
"private_key": "...",
"client_email": "...",
"client_id": "...",
"auth_uri": "...",
"token_uri": "...",
"auth_provider_x509_cert_url": "...",
"client_x509_cert_url": "..."
}
R script
#' Get a token to be used with ROhdsiWebApi using an IAP service account
#'
#' @param jsonPath File path to the service account json downloaded from google cloud platform
#' @param iapClientId The client id (e.g. "3434303000332-aaafdauuksdjjjjjjpalaaj3.apps.googleusercontent.com")
#'
#' @return A string containing a Bearer token
#' @export
fetchIapToken <- function(jsonPath, iapClientId) {
iapJson <- jsonlite::read_json(jsonPath)
iat <- floor(as.numeric(Sys.time()))
claim <- jose::jwt_claim(iss = iapJson$client_email,
aud = "https://www.googleapis.com/oauth2/v4/token",
exp = iat + 3600,
iat = iat,
target_audience = iapClientId)
secretKey <- openssl::read_key(iapJson$private_key)
jwt <- jose::jwt_encode_sig(claim = claim, key = secretKey, size = 256, header = NULL)
body <- list(grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion = jwt)
response <- httr::POST('https://oauth2.googleapis.com/token',
httr::content_type("application/x-www-form-urlencoded"),
# verbose(), # uncomment this to get more info regarding token request
encode = "form",
body = body)
idToken <- httr::content(response, as = "parsed")
authHeader <- paste("Bearer", idToken$id_token)
stopifnot(is.character(authHeader))
authHeader
}
# A simple example
library(ROhdsiWebApi)
baseUrl <- "https://atlas-url.com/WebAPI"
# Get the Bearer token
authHeader <- fetchIapToken(jsonPath = "example_service_account.json",
iapClientId = "numbers-lettersAndNumbers.googleusercontent.com")
# Associate the bearer token with the baseUrl in the package environment
setAuthHeader(baseUrl, authHeader)
# Use ROhdsiWebApi functions as normal
getCdmSources(baseUrl)
Hi @ablack3 -- I added a working example from our Active Directory setup here: https://github.com/OHDSI/WebAPI/issues/1473#issuecomment-780915439
so setAuthHeader() would take the baseUrl and the bearer token, no need to modify the other function calls, right? And then the function code would include an add_headers(Authorization = bearer)
parameter for GETs.
Edit: just saw your PR, looks like you already are handling AD auth. Nice!
It is currently not possible to connect to a WebAPI that has security enabled. This may require some changes in WebAPI as well.