You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何从shinyapps.io应用认证Plumber API调用?密钥认证是否安全?

Great question! Let's break this down clearly since you're building a private Shiny app on shinyapps.io that talks to a Plumber API—security here is critical to keep your data safe. I'll cover both your authentication implementation question and the security of using .Renviron keys in headers.

Implementing Authentication Between Shiny (shinyapps.io) and Plumber API

You have two solid approaches here, depending on whether you want to validate just the app itself or also individual users:

1. API Key Authentication (Your Proposed Method)

This is the simplest way to ensure only your Shiny app can call the Plumber API. Here's how to set it up:

Shiny App Side (shinyapps.io)

  • Store your API key in a .Renviron file locally, but don't include this file in your deployment bundle. Instead, add the key directly in the shinyapps.io dashboard under Settings > Environment Variables (shinyapps.io encrypts these at rest).
  • When making API calls, pull the key with Sys.getenv("PLUMBER_API_KEY") and include it in the request headers. Use httr2 (recommended over httr for modern workflows) to build authenticated requests:
    library(shiny)
    library(httr2)
    
    server <- function(input, output, session) {
      # Fetch the API key from environment variables
      api_key <- Sys.getenv("PLUMBER_API_KEY")
      
      # Example authenticated API call
      get_data <- reactive({
        req <- request("https://your-plumber-api-url.com/data-endpoint") %>%
          req_headers(`X-API-KEY` = api_key) %>% # Custom header for API key
          req_perform() %>%
          resp_body_json()
      })
      
      # Use the data in your app...
    }
    

Plumber API Side

Add a filter to validate the API key on every incoming request. This runs before any endpoint logic:

library(plumber)

# Initialize your Plumber API
pr <- plumb("your-plumber-file.R")

# Add a validation filter
pr$filter("validate_api_key", function(req, res) {
  # Fetch the expected key from your cloud platform's environment variables
  expected_key <- Sys.getenv("PLUMBER_API_KEY")
  # Get the key provided in the request header
  provided_key <- req$HTTP_X_API_KEY
  
  # Check if the key is missing or invalid
  if (is.null(provided_key) || provided_key != expected_key) {
    res$status <- 401 # Unauthorized
    return(list(error = "Invalid or missing API key"))
  }
  
  # If valid, proceed to the endpoint
  forward()
})

# Run the API
pr$run(host = "0.0.0.0", port = 8000)

2. User-Level Authentication (Leveraging shinyapps.io's Auth)

Since your Shiny app is private (users log in via shinyapps.io), you can extend this auth to the Plumber API to restrict access per user:

Shiny App Side

Get the logged-in user's details (shinyapps.io automatically sets this) and pass it to the API alongside the app's API key:

server <- function(input, output, session) {
  api_key <- Sys.getenv("PLUMBER_API_KEY")
  # Get the current user's email from shinyapps.io auth
  current_user <- shiny::getShinyOption("user")$email
  
  get_user_data <- reactive({
    req <- request("https://your-plumber-api-url.com/user-specific-data") %>%
      req_headers(
        `X-API-KEY` = api_key,
        `X-USER-EMAIL` = current_user
      ) %>%
      req_perform() %>%
      resp_body_json()
  })
}

Plumber API Side

Add a filter that validates both the API key and the user's authorization (e.g., check if the user is in an approved list):

pr$filter("validate_user", function(req, res) {
  # First validate the app's API key
  expected_api_key <- Sys.getenv("PLUMBER_API_KEY")
  provided_key <- req$HTTP_X_API_KEY
  
  if (is.null(provided_key) || provided_key != expected_api_key) {
    res$status <- 401
    return(list(error = "Invalid API key"))
  }
  
  # Then validate the user is allowed
  user_email <- req$HTTP_X_USER_EMAIL
  allowed_users <- strsplit(Sys.getenv("ALLOWED_USERS"), ",")[[1]] # e.g., "user1@example.com,user2@example.com"
  
  if (!user_email %in% allowed_users) {
    res$status <- 403 # Forbidden
    return(list(error = "User not authorized to access this resource"))
  }
  
  forward()
})
Is Adding a .Renviron-Defined Key to HTTP Headers Secure?

This approach is safe for most use cases if you follow best practices, but there are important caveats to consider:

Pros

  • No hardcoding: Keys aren't stored in your codebase, so they won't leak if you push code to version control (like GitHub).
  • shinyapps.io security: Environment variables on shinyapps.io are encrypted at rest, so they can't be accessed via your app's code or deployment logs.
  • Simple to implement: No complex OAuth flows needed for small private apps.

Risks & Mitigations

  • MITM attacks: If your Plumber API isn't served over HTTPS, the API key in headers can be intercepted in plaintext. Fix: Always use HTTPS for both your Shiny app (shinyapps.io uses HTTPS by default) and your Plumber API.
  • Log exposure: Accidental logging of API requests in your Shiny app or Plumber API could leak the key. Fix: Disable verbose request logging, and never print the key in error messages or debug output.
  • Static key limitations: If the key is compromised, anyone can access your API until you rotate it. Fix: Implement regular key rotation, and consider using short-lived keys (e.g., JWT tokens) if you need higher security.
  • Scope: This method only validates that the request comes from your Shiny app—not which user is making it. If you need per-user permissions, combine it with the user-level auth approach above.

Final Verdict

For a private, small-to-medium scale app, using an environment-stored API key in HTTPS headers is a secure and practical choice. For enterprise-level security, you might want to upgrade to OAuth2 client credentials flow, but the API key method is a strong starting point.

内容的提问来源于stack exchange,提问作者vwrobel

火山引擎 最新活动