How do I get Shiny-server to working with Azure Active Directory

Viewed 1844

I am using Azure Webapps for Containers to host an R Shiny-Server. I want to use Azure Active Directory to authenticate and authorize the user logging into the app.

I am using the rocker/shiny image from dockerhub and the image builds and run easily. However, when I am turning on Active Directory the app does not work anymore. Any hints and clues on what might be wrong would be of great help.

Working

Not working

2 Answers

I got the same problem of an "empty" page, because loading of static files by the browser returned HTTP 400 when activating AD authentication. I have a Shiny app in a docker container on Azure App Services on the latest version of Shiny server (v1.5.12.933) and Shiny (1.4.0).

This means the problem described here https://community.rstudio.com/t/shiny-v1-3-known-regressions-and-serious-issues/28180/4 which I suspected first is not the reason.

w/o AD authentication the page is displayed correctly. The Azure proxy responsible for AD authentication injects some HTTP headers and cookies. I inspected the full HTTP request on server side via tcpflow -p -c -i eth0 port 3838 and had a look at the underlying R library httpuv which is responsible for the HTTP connection to the Shiny server.

When searching where in this library HTTP 400 codes are returned I found https://github.com/rstudio/httpuv/blob/master/src/webapplication.cpp and the following code snippet

// Make sure that there's no message body.
  if (pRequest->hasHeader("Content-Length") || pRequest->hasHeader("Transfer-Encoding")) {
    return error_response(pRequest, 400);
  }

while the request arriving at the server had the header Content-Length: 0 which is not present if AD authentication is turned off.

I created a fix and PR for httpuv, see issue https://github.com/rstudio/httpuv/issues/247.

You can use it as long as it is not merged into their repo.

Just run

R -e 'library(devtools); install_github("LHaferkamp/httpuv")'

in your Dockerfile

First you need to register your application in the Azure app registration service and get a tenant and app id. read this [quickstart][1] for the details. You use the Azure active directory by get_azure_token function from AzureAuth library. read more about the arguments [here][2].

    ##################################
######### Installing libraires #################
load.lib <- c("AzureAuth","shiny","shinyjs","httr")

install.lib <- load.lib[!load.lib %in% installed.packages()]
for(lib in install.lib) install.packages(lib,dependencies=TRUE)
sapply(load.lib,library,character=TRUE)
##############################################
######### Setting the local port ###############
redirect <- "http://localhost:8100"

port <- httr::parse_url(redirect)$port
options(shiny.port=if(is.null(port)) 80 else as.numeric(port))
##################################################
######### Authentication #######################
tenant <- "your-tenant-here from Azure app service"
app <- "your-app-id-here from azure app service"
resource <- "your-scopes-here"
#example 
resource <- c("https://graph.microsoft.com/User.Read.All",
                  "https://graph.microsoft.com/User.ReadWrite.All",
                  "https://graph.microsoft.com/Directory.ReadWrite.All",
                  "offline_access")
pass = "your secret that you generate in the Azure app regitration"
aad_host = "https://login.microsoftonline.com/common/oauth2"

Token <- AzureAuth::get_azure_token( resource,tenant,
                                       app,
                                       password = pass,
                                       auth_type="authorization_code",
                                       authorize_args=list(redirect_uri=redirect),
                                       use_cache=TRUE,
                                       auth_code=opts$code,
                                       version = 2,
                                       aad_host = aad_host
  )


###############Importing the app R files#########
# load ui elements
source("ui.R")
# load server function
source("server.R")
#################################################

ui_func <- function(req)
{
  opts <- parseQueryString(req$QUERY_STRING)
  if(is.null(opts$code))
  {
    auth_uri <- build_authorization_uri(resource, tenant, app, redirect_uri=redirect, version=2)
    redir_js <- sprintf("location.replace(\"%s\");", auth_uri)
    tags$script(HTML(redir_js))
  }
  else ui
}

# Run the application
shinyApp(ui = ui_func, server = server)

I tried to explain each factor in the comment in the code above. If you use version=1 Authentication you need to save access token and refresh token somewhere in your code later on and get a new access token after the expiry. if you use version=2 ,you simply Token$refresh somewhere in your server.R and it'll extends your certificate. The ui_func function in the end is to establish the azure authorization sign in page if the users are not authenticated. [1]: https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app [2]: https://www.rdocumentation.org/packages/AzureAuth/versions/1.0.2/topics/get_azure_token

Related