Skip to contents

Preamble

By default, csiapps runs in sandbox mode, so a wrapped app can be developed and run locally without any of the OAuth client credentials below: the login redirect is simulated (see Sandbox mode at the end of this article). The following environment variables are only required for a real deployment, once sandbox mode is turned off:

  • CSIAPPS_CLIENT_ID: The client ID for the application registered in CSIAPPS.
  • CSIAPPS_CLIENT_SECRET: The client secret for the application registered in CSIAPPS.
  • CSIAPPS_REDIRECT_URL: The URL to which the application will redirect after authentication.
  • CSIAPPS_SCOPE: (optional) The scope of the authentication request.

Example

Suppose that you have the following shiny web application:

library(shiny)

df = faithful[, 2]

ui <- fluidPage(

    titlePanel("Old Faithful Geyser Data"),

    sidebarLayout(
        sidebarPanel(
            sliderInput("bins",
                        "Number of bins:",
                        min = 1,
                        max = 50,
                        value = 30)
        ),

        mainPanel(
           plotOutput("distPlot")
        )
    )
)

server <- function(input, output) {

    output$distPlot <- renderPlot({
        bins <- seq(min(df), max(df), length.out = input$bins + 1)

        hist(df, breaks = bins, col = 'darkgray', border = 'white',
             xlab = 'Waiting time to next eruption (in mins)',
             main = 'Histogram of waiting times')
    })
}

shinyApp(ui = ui, server = server)

To migrate this app within the CSIAPPS ecosystem, we can leverage several functions provided by csiapps.

1. set_institute()

We specify which institute internal API calls should be made for (such as authentication redirects) by the application using set_institute().

# institute can be set to one of "csiontario" or "csipacific"
csiapps::set_institute("csiontario")

2. check_secrets()

We then run check_secrets() to ensure that all environment variables required have been set. The verbose argument, which is FALSE by default, can be set to TRUE to print out the values of the environment variables that are being checked. check_secrets() will throw an error if any of the required environment variables are not present, making it useful for debugging.

csiapps::check_secrets(verbose = FALSE)

3. global_wrapper()

For code defined outside but used within the server function, we use global_wrapper() to ensure that it is accessible by internal helper functions.

csiapps::global_wrapper({
  df = faithful[, 2]
})

4. ui_wrapper() and server_wrapper()

The ui page and server function can simply be wrapped by ui_wrapper() and server_wrapper(), respectively. These convenience functions include additional code to redirect the application to CSIAPPS for user authentication and provide aesthetic formatting.

# original code

ui <- ...

server <- ...

shinyApp(ui = ui, server = server)

# wrapped code

shinyApp(ui = csiapps::ui_wrapper(ui), server = csiapps::server_wrapper(server))

Summary

Therefore, the full code for the app, after migration, would look like this:

library(shiny)
library(csiapps)

# CSIAPPS Setup
set_institute("csiontario")
check_secrets()

global_wrapper({
  df = faithful[, 2]
})

# Define UI for application that draws a histogram
ui <- fluidPage(

    titlePanel("Old Faithful Geyser Data"),

    sidebarLayout(
        sidebarPanel(
            sliderInput("bins",
                        "Number of bins:",
                        min = 1,
                        max = 50,
                        value = 30)
        ),

        mainPanel(
           plotOutput("distPlot")
        )
    )
)

# Define server logic required to draw a histogram
server <- function(input, output) {

    output$distPlot <- renderPlot({
        bins <- seq(min(df), max(df), length.out = input$bins + 1)

        # draw the histogram with the specified number of bins
        hist(df, breaks = bins, col = 'darkgray', border = 'white',
             xlab = 'Waiting time to next eruption (in mins)',
             main = 'Histogram of waiting times')
    })
}

# Run the application
shinyApp(ui = ui_wrapper(ui), server = server_wrapper(server))

Sandbox mode

The code above is written for production, but you do not need any client credentials or a running identity provider to develop it. By default csiapps is in sandbox mode, and server_wrapper() simulates the login redirect: instead of sending the browser to CSIAPPS to authenticate, it seeds the session from the CSIAPPS_ACCESS_TOKEN you already have in your environment.

Sys.setenv(CSIAPPS_ACCESS_TOKEN = "your-dev-access-token")

With that token set, the wrapped app runs locally and the header loads your real identity (/me) from the API, exactly as it would after a production login. The token is used only to emulate the login — everything else (sport organizations, athletes, warehouse records) is dummy, served from the local sandbox (see create_sport_org(), create_profile(), and vignette("api")). check_secrets() reports whether a token was found rather than erroring on the (unneeded) OAuth secrets. If no token is set, the app shell still renders but shows an unauthenticated notice prompting you to set a read-only CSIAPPS_ACCESS_TOKEN.

Note: Sandbox mode never touches real client data. Warehouse calls made through make_request() are emulated locally, and registration reads (fetch_org_options(), fetch_profiles()) come from the local dummy registry. Only /me uses your real token — call set_institute() to match the institute that issued it, or that lookup is rejected.

Deploying

Because the app code is identical in both modes, deploying is just a matter of turning sandbox mode off — no code changes required:

options(csiapps.sandbox = FALSE) # or set CSIAPPS_ENV=production

Once your app is wrapped and ready to deploy, generate a manifest.json from the app directory. The deployment target (Posit Connect / shinyapps.io) uses it to reproduce your package environment — including csiapps — so it must be regenerated whenever your dependencies change:

rsconnect::writeManifest()

Commit the resulting manifest.json alongside your app.