This vignette describes how to incorporate Microsoft365R and interactive authentication with Azure Active Directory (AAD) into a Shiny web app. There are a few steps involved:
The default Microsoft365R app registration only works when the package is used on a local machine; it does not support running in a remote server. Because of this, when you use Microsoft365R inside a Shiny app, you (or your friendly local sysadmin) must register that app in AAD.
The main things to set in your app registration are:
The redirect URI of your app, ie, your
user-facing site address. For example if your app is hosted in
shinyapps.io, this would be a URL of the form
https://youraccount.shinyapps.io/appname
. If your app uses
a special port number rather than the default port 443 for HTTPS, don’t
forget to include that as well. It’s possible to set more than one
redirect, so you can reuse a single app registration for multiple Shiny
apps.
The type of redirect, either native (mobile & desktop) or webapp. There are also other types of redirects, but these are the only ones relevant to R. The difference between a mobile & desktop and a webapp redirect is that you supply a client secret when authenticating with the latter, but not the former. It’s recommended to use a webapp redirect for a Shiny app, as the client secret helps prevent third parties from hijacking your app registration. The client secret is also set as part of the app registration.
The intended audience of your app, ie, who is allowed to use it. This can be only members of your AAD tenant; members of any AAD tenant; or anyone with a Microsoft account (including personal accounts).
The permissions required by your app. Refer to the app_registration.md file for the list of permissions Microsoft365R uses. You can omit any permissions that you don’t need if your app doesn’t use all of Microsoft365R’s functionality, eg if you don’t handle emails you can omit Mail.Send and Mail.ReadWrite.
The following pages at the AAD documentation will be helpful:
A step-by-step guide to registering an app in the Azure portal.
How to set permissions for an app. For a Shiny app, note that you want delegated permissions from the Microsoft Graph API, not application permissions.
Below is a basic app that logs the user in, retrieves their OneDrive, and lists the contents of the root folder.
One thing to note is that the regular Microsoft365R client functions
like get_sharepoint_site
, get_team
etc are
intended for use on a local machine. While they will still work when
called in a web app, it’s a better idea to call the underlying R6
methods directly: Microsoft365R extends AzureGraph with several R6
classes and methods, which do the actual work of interacting with the
Microsoft 365 REST API.
Here, we call the get_drive()
method for the
AzureGraph::az_user
class, which retrieves the OneDrive for
a user. For more information, see the online help page in R for the
Microsoft365R “add_methods” topic: ?add_methods
.
library(AzureAuth)
library(AzureGraph)
library(Microsoft365R)
library(shiny)
tenant <- "your-tenant-here"
# the application/client ID of the app registration you created in AAD
# - not to be confused with the 'object ID' or 'service principal ID'
app <- "your-app-id-here"
# the address of your app: also the redirect URI of your app registration
# - AAD allows only HTTPS for non-localhost redirects, not HTTP
redirect <- "https://example.com/mysite"
port <- httr::parse_url(redirect)$port
options(shiny.port=if(is.null(port)) 443 else as.numeric(port))
# if your app reg has a 'webapp' redirect, it requires a client secret (password)
# - you should NEVER put secrets in code: here we get it from an environment variable
# - leave the environment variable unset if you have a 'desktop & mobile' redirect
pwd <- Sys.getenv("EXAMPLE_SHINY_CLIENT_SECRET", "")
if(pwd == "") pwd <- NULL
# get the Graph permissions listed for the app, plus an ID token
resource <- c("https://graph.microsoft.com/.default", "openid")
# a simple UI: display the user's OneDrive
ui <- fluidPage(
verbatimTextOutput("drv")
)
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
}
server <- function(input, output, session)
{
opts <- parseQueryString(isolate(session$clientData$url_search))
if(is.null(opts$code))
return()
token <- get_azure_token(resource, tenant, app, password=pwd, auth_type="authorization_code",
authorize_args=list(redirect_uri=redirect), version=2,
use_cache=FALSE, auth_code=opts$code)
# display the contents of the user's OneDrive root folder
drv <- ms_graph$
new(token=token)$
get_user()$
get_drive()
output$drv <- renderPrint(drv$list_files())
}
shinyApp(ui_func, server)