Authenticating from Shiny

Hong Ooi

Because a Shiny app has separate UI and server components, the interactive authentication flows require some changes. In particular, the authorization step (logging in to Azure) has to be conducted separately from the token acquisition step.

AzureAuth provides the build_authorization_uri function to facilitate this separation. You call this function to obtain a URI that you browse to in order to login to Azure. Once you have logged in, Azure will return an authorization code as part of a redirect.

Here is a skeleton Shiny app that demonstrates its use. The UI calls build_authorization_uri, and then redirects your browser to that location. When you have logged in, the server captures the authorization code and calls get_azure_token to obtain the token. Once the token is obtained, shinyjs is used to return the URL back to its original state.

library(AzureAuth)
library(shiny)
library(shinyjs)

tenant <- "your-tenant-here"
app <- "your-app-id-here"

# the Azure resource permissions needed
# if your app doesn't use any Azure resources (you only want to do authentication),
# set the resource to "openid" only
resource <- c("https://management.azure.com/.default", "openid")

# set this to the site URL of your app once it is deployed
# this must also be the redirect for your registered app in Azure Active Directory
redirect <- "http://localhost:8100"

port <- httr::parse_url(redirect)$port
options(shiny.port=if(is.null(port)) 80 else as.numeric(port))

# replace this with your app's regular UI
ui <- fluidPage(
    useShinyjs(),
    verbatimTextOutput("token")
)

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
}

# code for cleaning url after authentication
clean_url_js <- sprintf(
    "
    $(document).ready(function(event) {
      const nextURL = '%s';
      const nextTitle = 'My new page title';
      const nextState = { additionalInformation: 'Updated the URL with JS' };
      // This will create a new entry in the browser's history, without reloading
      window.history.pushState(nextState, nextTitle, nextURL);
    });
    ", redirect
)

server <- function(input, output, session)
{
    shinyjs::runjs(clean_url_js)

    opts <- parseQueryString(isolate(session$clientData$url_search))
    if(is.null(opts$code))
        return()

    # this assumes your app has a 'public client/native' redirect:
    # if it is a 'web' redirect, include the client secret as the password argument
    token <- get_azure_token(resource, tenant, app, auth_type="authorization_code",
                             authorize_args=list(redirect_uri=redirect), version=2,
                             use_cache=FALSE, auth_code=opts$code)

    output$token <- renderPrint(token)
}

shinyApp(ui_func, server)

Note that this process is only necessary within a web app, and only when using an interactive authentication flow. In a normal R session, or when using the client credentials or resource owner grant flows, you can simply call get_azure_token directly.