Skip to contents

Introduction

The spdgt.auth package provides authentication utilities for SpeedGoat APIs, designed primarily for package developers rather than individual users. This package supports the following scenarios:

  1. Interactive sessions: When users write and run code in an R console.
  2. Non-interactive sessions: When scripts are executed automatically, e.g., via a cron job.
  3. Graphical User Interfaces (GUI): For web-based GUIs like Shiny apps.

This vignette explains how to authenticate in each scenario and demonstrates how to integrate spdgt.auth into your R package.

Authentication scenarios

Interactive sessions

In interactive sessions, calling a function that requires authentication will redirect the user to the SpeedGoat login page. After logging in, users are instructed to return to R, where an authentication token is stored for the session.

  • Token expiration: Tokens are refreshed automatically, and users are notified if this happens.
  • Session persistence: Authentication is session-based and does not persist across R restarts.

Non-interactive sessions

For non-interactive sessions (e.g., cron jobs, CI pipelines), authentication uses an API key instead of the browser-based OAuth flow. Set the SPDGT_API_KEY environment variable in your .Renviron file:

SPDGT_API_KEY=your-counts-api-key

auth_login() is called automatically on your first API request — you do not need to call it yourself.

Note: API key authentication is currently only available for the counts realm. The telemetry realm uses OAuth for interactive sessions.

Graphical User Interfaces (GUI)

When a Shiny app runs behind ShinyProxy, authentication is handled via OpenID Connect (OIDC). ShinyProxy authenticates the user and passes a token to the app through environment variables. The package detects this automatically — no user action is required.

The counts realm checks the SHINYPROXY_OIDC_ACCESS_TOKEN environment variable. The package also reads SHINYPROXY_USERNAME to verify that the token matches the logged-in user. OIDC for the telemetry realm is not yet supported in ShinyProxy.

For more details on OIDC, see the OpenID Connect documentation.

Realms

Authentication is organized by realms. A realm is a named group of APIs that share a single token authority. For example, the "counts" realm includes the counts API and any satellite APIs (e.g., sightability) that trust its tokens. Each realm stores its own token, refresh token, expiration, and auth type independently.

This means that authenticating against the counts API does not give you access to the telemetry API, and vice versa. Token refresh, expiration, and login are all handled per-realm.

All functions default to realm = "counts", so existing code works without changes. To explicitly log in to a specific realm:

# Log in to the telemetry realm
auth_login(realm = "telemetry")

In most cases you do not need to call auth_login() yourself — it is called automatically when you make your first API request to a given realm.

Telemetry realm

The telemetry realm (marks.spdgt.com) has its own token database, separate from counts. A token from one realm does not work on the other. The package handles this automatically — when you call a telemetry API function (e.g., from spdgt.telem), authentication targets telemetry-api directly.

Available auth methods for the telemetry realm:

Method How Status
OAuth Interactive browser flow Working
OIDC ShinyProxy apps deployed against telemetry-api Working
API key SPDGT_API_KEY_TELEMETRY env var Pending server-side support

For interactive use, authentication is seamless — the browser opens telemetry-api’s login page. For ShinyProxy apps, the OIDC token is detected automatically from SHINYPROXY_OIDC_ACCESS_TOKEN.

Important: The telemetry realm is registered by spdgt.telem (or spdgt.telem.admin). You must load one of these packages before calling telemetry API functions:

library(spdgt.telem)
# Now telemetry API calls will authenticate automatically

Integrating spdgt.auth into your package

Registering APIs

In your package’s R/zzz.R, use add_api() to register your API endpoints. The spdgt.auth package initializes a shared auth state at load time; downstream packages register their APIs into it.

.onLoad <- function(libname, pkgname) {
  # Register API — creates a new "my-api" realm

  spdgt.auth::add_api("my-api", "https://my-api.spdgt.com/api")
}

If your API shares a token with an existing realm (e.g., a satellite API that trusts the counts API’s tokens), use the realm parameter:

.onLoad <- function(libname, pkgname) {
  # Join the "counts" realm — shares its token
  spdgt.auth::add_api(
    "sightability",
    "https://sightability.spdgt.com/api",
    realm = "counts"
  )
}

Creating an API function

Define a function in your package that performs an authenticated API call:

#' Get projects from the API
#'
#' @param ... Additional arguments passed to `api_get()`
#' @return A data frame of projects
#' @export
get_projects <- function(...) {
  spdgt.auth::api_get(
    api_name = "my-api",
    endpoint = "projects",
    ...
  )
}

Authentication happens automatically — api_perform() (called internally by api_get()) detects the realm from the request and triggers login if needed. In interactive sessions this opens an OAuth browser flow; in non-interactive sessions credentials must already be configured (API key or OIDC token).

Using multiple realms

When your package needs to call APIs in different realms, register each API with the appropriate realm:

.onLoad <- function(libname, pkgname) {
  # These two APIs share the "counts" token
  spdgt.auth::add_api("counts", "https://counts.spdgt.com/api")
  spdgt.auth::add_api(
    "sightability",
    "https://sightability.spdgt.com/api",
    realm = "counts"
  )

  # This API has its own realm and token
  spdgt.auth::add_api("telemetry", "https://telemetry.spdgt.com/api")
}

When making requests, api_perform() automatically resolves the correct realm and token from the request. No extra work is needed in your API functions.

Token forwarding (Plumber APIs)

For Plumber APIs that receive authenticated requests and need to make downstream API calls, use auth_use_token():

#* @filter auth
function(req, res) {
  if (spdgt.auth::auth_use_token(
    req$HEADERS["authorization"],
    validate = TRUE
  )) {
    plumber::forward()
  } else {
    res$status <- 401
    list(error = "Unauthorized")
  }
}

To forward a token to a non-default realm:

spdgt.auth::auth_use_token(token, realm = "telemetry")

Error handling

api_perform() aborts with a structured error on any non-2xx response. The error message includes the HTTP status code, the server’s error message, and any field-level validation errors.

For example, a 422 validation error produces:

Error:
! API request failed with status 422 for realm "counts"
x The given data was invalid.
i email: The email field is required.
i name: The name field is required.

On a 401 response, api_perform() automatically attempts to refresh the token and retry the request once. If the retry also fails, it aborts with the error.

Troubleshooting

A few ideas to help with authentication errors:

  • Try restarting R
  • Try clearing your browser history or use incognito mode
  • Try using a different browser
  • Call auth_logout() to clear all stored credentials, then try again
  • To clear a single realm: auth_logout(realm = "counts")

Summary

The spdgt.auth package simplifies authentication for SpeedGoat APIs in R. By following the examples in this vignette, you can:

  • Authenticate users in interactive, non-interactive, and GUI contexts.
  • Manage authentication seamlessly across multiple APIs and realms.
  • Handle errors from API responses with structured, actionable messages.

For more information, refer to the package documentation.