Authentication
authentication.RmdIntroduction
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:
- Interactive sessions: When users write and run code in an R console.
- Non-interactive sessions: When scripts are executed automatically, e.g., via a cron job.
- 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.
count-api is the identity provider for the whole ecosystem. Signing in to any realm authenticates against counts, and the other realms are served from that one credential — they hold a reference to it rather than a copy, so a refresh or a project switch is visible everywhere at once. One sign-in covers everything; you are not asked to authenticate a second time when you move between realms.
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
Telemetry data comes from a different API
(marks.spdgt.com), but you sign in once for both. Calling a
telemetry function after working with counts needs no second login, and
starting with telemetry does not make you sign in again for a counts
lookup later.
One thing is worth knowing, because it is easy to miss. Your sign-in is tied to a project, and telemetry will not accept a session that has none. Almost every account is assigned a project, so this never comes up. Administrator accounts are not, and choose a project on the sign-in page instead.
If auth_status() shows no project, that is what has
happened — and it cannot be fixed after the fact, because the project is
part of the sign-in. Run auth_logout(), then make any API
call to be prompted again, and pick a project this time.
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 automaticallyRegions
The ecosystem is deployed in more than one country. Projects based in
Canada use counts.ca.spdgt.com and
marks.ca.spdgt.com; everything else uses the unprefixed US
hosts.
Set the region once, in .Renviron, and restart R:
SPDGT_REGION=ca
Leave it unset for the United States. spdgt_setup() will
show you this and open the file if you are not sure where it lives.
Every registered API is then addressed in that region. Packages declare one address per region, and the region picks between them when the URL is read, so it does not matter whether the region was set before or after the package loaded.
Check what a session resolves to with api_url():
api_url("counts")
#> "https://counts.ca.spdgt.com/api"Some services are deployed once and shared by every region — the
plumber APIs behind spdgt.sight and spdgt.sdm
among them, and the support site. Those declare a single address and
resolve to it everywhere.
If a package has not yet declared its addresses, a Canadian session stops with an error naming that package, rather than quietly sending the request to the United States. In a US session it works and says so once. Report the package name if you see either — the fix belongs in that package, not in your script.
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"
)
# A separate realm, still served by the counts credential
spdgt.auth::add_api("telemetry", "https://marks.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.