OAuth Integrations

Enhanced Advanced

Overview

Applications hosted on Posit Connect can be configured to acquire OAuth access tokens which allow publishers to author content with access to third-party protected resources. These access tokens are curated and provided to published content by Connect, but their associated permissions are defined externally. This allows data administrators to acquire and reuse their organization’s existing data access policies from a single centralized location.

Connect supports two types of OAuth integrations which can be used in published content.

Viewer integrations allow publishers to acquire and use the content viewer’s credentials when accessing protected resources, providing the viewer with a personalized view of the content. Viewers are only able to see the data they have been granted access to in the external system. Viewer integrations only support interactive content types. Content with access type Anyone - no login required and Share Links are not supported by Viewer integrations.

Service Account integrations allow publishers to acquire and use the credentials of a service account or service principal when accessing protected resources. Every viewer will have the same experience when interacting with the content. Service account integrations support both interactive and rendered content types. Additionally, access patterns using Anyone - no login required and Share Links are supported by Service Account integrations.

Supported content types for each auth type.
Auth Type Interactive Rendered Static
Viewer Yes No No
Service Account Yes Yes No
Supported access patterns for each auth type.
Auth Type Public Access Share Links
Viewer No No
Service Account Yes Yes

For more information on configuring OAuth integrations in Connect, see the OAuth Integrations section of the Admin Guide.

Authoring content

Connect provides OAuth access tokens to content through its credential exchange endpoint, which is an implementation of an OAuth Token Exchange.

When authoring content that requires access to protected resources, publishers should use the Posit SDK for Python, connectapi for R, or call the Connect Server API’s /v1/oauth/integrations/credentials endpoint directly to obtain an OAuth access token.

Security considerations

Publishers must take care to ensure that access tokens are not leaked through content logs, cached incorrectly by the content, or otherwise misused. Each Python and R web application framework exposes session/application state differently.

It is up to the publisher to understand in which context(s) it is appropriate to request and use OAuth credentials.

Obtaining a Viewer OAuth access token

In order for content to obtain a viewer’s OAuth access token, the following criteria must be met:

  • The content must be deployed to Connect.
  • The content must be associated with a Viewer OAuth integration.
  • The viewer must visit the running content on Connect.
  • The viewer must log in to the OAuth integration on Connect.

When content is running on Connect, a short-lived user-session-token is attached to the Posit-Connect-User-Session-Token HTTP header for all interactive content requests. The user-session-token serves as the subject_token in the credential exchange request.

The following examples demonstrate how to perform a credential exchange in order to obtain the viewer’s access token from Connect.

Note

This example uses the Shiny for Python web application framework and the posit-python-sdk to perform the credential exchange.

from posit import connect
from shiny.express import render, session

# initialize the Connect client
# runs once per startup
client = connect.Client()

# runs once per session
@render.text
def access_token():
    # read the user-session-token header
    user_session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")

    # fetch the viewer's access token
    return client.oauth.get_credentials(user_session_token).get("access_token")
Note

This example uses the Shiny for R web application framework and the connectapi package to perform the credential exchange.

library(connectapi)
library(shiny)

ui <- fluidPage(textOutput("access_token"))

server <- function(input, output, session) {

  # initialize the Connect client
  client <- connect()

  # read the user-session-token header
  user_session_token <- session$request$HTTP_POSIT_CONNECT_USER_SESSION_TOKEN

  output$access_token <- renderText({
    # fetch the viewer's access token
    credentials <- get_oauth_credentials(client, user_session_token)
    credentials$access_token
  })
}

# start the Shiny app
shinyApp(ui = ui, server = server)

Access tokens are short-lived, usually expiring within 24 hours of when they were issued. Content that utilizes access tokens must be resilient to errors that can occur when the access token expires. To simplify this for content authors, Connect is responsible for access token refresh. Requesting a viewer’s access token from the /v1/oauth/integrations/credentials endpoint always returns a refreshed access token.

Connect encrypts and stores the viewer’s access token and refresh token with their OAuth session. If the viewer’s access token is valid, it is returned right away. If the viewer’s access token is expired (or about to expire) then Connect will attempt to refresh the OAuth Access Token first, before returning it to the requester (the content).

OAuth refresh tokens are never returned by Connect.

Obtaining a Service Account OAuth access token

In order for content to obtain a service account’s OAuth access token, the following criteria must be met:

  • The content must be deployed to Connect.
  • The content must be associated with a Service Account OAuth integration.

When content is running on Connect, a short-lived content-session-token is set in the CONNECT_CONTENT_SESSION_TOKEN environment variable for all interactive and rendered content processes. The content-session-token serves as the subject_token in the credential exchange request.

The following examples demonstrate how to perform a credential exchange in order to obtain a service account access token from Connect.

Note

This example uses the posit-python-sdk to perform the credential exchange.

from posit import connect

client = connect.Client()
# NOTE: get_content_credentials reads the `CONNECT_CONTENT_SESSION_TOKEN`
# from the environment to complete the credential exchange.
print(client.oauth.get_content_credentials().get("access_token"))
Note

This example uses the connectapi package to perform the credential exchange.

library(connectapi)

client <- connect()
# NOTE: get_oauth_content_credentials reads the `CONNECT_CONTENT_SESSION_TOKEN`
# from the environment to complete the credential exchange.
resp <- get_oauth_content_credentials(client)
resp$access_token

Connect does not acquire a refresh token when a Service Account credential exchange occurs. It also does not store the acquired access token in a session. On every Service Account credential exchange, Connect requests a new access token from the external system, so every request to the credential exchange endpoint will acquire a fresh access token.

Local development

OAuth integrations can only be used to obtain an OAuth access token when the content is running on a Connect server, so testing local changes to content can be challenging. A good strategy for authoring content that uses OAuth integrations is to first check whether the content is running on a Connect server, and then fall-back to the developer’s locally configured credentials when it is not. All content running on a Connect server has the following environment variable set: RSTUDIO_PRODUCT=CONNECT. This environment variable can be used by the content to determine if it is running on a Connect server.

The Connect cookbook contains examples which use the Posit SDK for Python and the connectapi R package to implement local development fallback. For example, see the Python Databricks Viewer OAuth cookbook for an example that uses a Databricks OAuth integration with various Python application frameworks.

The Posit SDK for Python and the connectapi R package are under active development and contributions are welcome.

Adding OAuth integrations to deployed content

Once the content has been deployed to Connect, the publisher associates the OAuth integration with their content.

This is accomplished using the dashboard by visiting the Access tab in the Content Settings pane, where the publisher will see an Integrations section with an Add integration button.

The Access tab provides an Add integration button.

Clicking this button will open a modal which allows the Connect publisher to select an OAuth integration.

Example of publisher OAuth integration selection.

For rendered content, the list of available integrations in the modal will be filtered to only include Service Account integrations. Viewer integrations are not supported for rendered content.

After selecting an integration and saving, the Add integration button is replaced with a panel containing information about the integration.

Completed Service Account OAuth integration selection.

Viewer OAuth integrations also provide a button to log in to the OAuth integration.

Completed Viewer OAuth integration selection.

Alternatively, the example below uses curl and the Connect Server API to associate an OAuth integration with an existing piece of deployed content in Connect.

Note

Replace connect.example.org with the address of the Connect server.

Terminal
# list all available oauth integrations
curl -H "Authorization: Key ${CONNECT_API_KEY}" \
  -XGET https://connect.example.org/__api__/v1/oauth/integrations

# assign an oauth integration to a piece of content
curl -H "Authorization: Key ${CONNECT_API_KEY}" \
  -XPUT https://connect.example.org/__api__/v1/content/<content-guid>/oauth/integrations/associations \
  --data '[
    {"oauth_integration_guid": "<oauth-integration-guid>"}
  ]'

Viewing content on Connect

Viewer OAuth integrations

Connect users who have the Viewer permission on the content item receive a personalized view when they visit a deployed application which uses a Viewer OAuth integration.

OAuth integration viewer impersonation in Posit Connect.

Upon visiting a piece of content that is associated with a Viewer OAuth integration for the first time, if the user is accessing the content through the Connect dashboard, they are prompted to log in.

The login modal only appears if the user is not already logged in and the content settings pane is closed. The Login button is also available on the Access tab of the Content Settings pane.

The Login modal displayed on the viewer's first visit to the content.

When interacting with the content in open-solo mode and the viewer is not already logged in, then the viewer is automatically redirected to the login URL of the OAuth integration in order to initialize their OAuth session.

OAuth sessions

An OAuth session holds metadata about the Connect user’s OAuth tokens. An OAuth session is created when a Connect user visits the /login endpoint of any OAuth integration. The login endpoint has the form: /__oauth__/integrations/<oauth-integration-guid>/login. In order to populate the OAuth session with an access token and refresh token, the user must complete the login flow by visiting the /login endpoint and successfully authenticating to the external service. After authenticating to the external service, the user is redirected back to Connect.

The user’s OAuth session and all OAuth tokens are deleted when the Connect user visits the /logout endpoint. The logout endpoint has the form: /__oauth__/integrations/<oauth-integration-guid>/logout.

OAuth sessions can also be managed using the /v1/oauth/sessions API endpoints. By default, Connect users can only manage their own OAuth sessions. Connect administrators can manage OAuth sessions of all Connect users. Managing an OAuth session does not allow the user to view OAuth tokens associated with the OAuth session. Access tokens are exposed only by the credential exchange endpoint.

OAuth refresh tokens are never returned by Connect.

An authenticated user in Connect can have only one OAuth session per Viewer OAuth integration. A given OAuth integration can be shared by multiple content items on Connect. The user only needs to log in to the OAuth integration once and the OAuth session remains valid for all pieces of content that use that OAuth integration. A user is considered to be logged in to the Viewer OAuth integration if there is an OAuth refresh token associated with the OAuth session.

A content viewer must visit a piece of content in order for the content to obtain the viewer’s access token. Simply associating an OAuth integration with a piece of content is not enough for the content to obtain the user’s access token.

This limitation prevents a content item from obtaining an arbitrary user’s access token, even when that user is assigned the Viewer role on the content. See the Admin guide’s security documentation for additional details.

Service Account OAuth integrations

Connect users who have the Viewer permission on the content item have access to the permissions of the configured service account when using a Service Account integration. This means that the data will look the same for each user.

Because viewers are not granting access to their own OAuth tokens, but are instead using those of the Service Account, there is no login flow for the user to go through when they interact with the content. There are no login buttons, and the open solo view does not perform an automatic redirect. There are no OAuth sessions, because viewer OAuth credentials are not being maintained by Connect.

Instead, whenever the content executes, either because a viewer is interacting with the content or a render request is issued, Connect uses the service account credentials configured in the integration to request a fresh OAuth access token from the third-party service. Connect only performs this request for content which has (a) been associated with the Service Account integration, and (b) is actively executing, either as an interactive web application or a report render.