Api Key Exposure in Rocket with Openid Connect
Api Key Exposure in Rocket with OpenID Connect — how this specific combination creates or exposes the vulnerability
Rocket is a web framework for Rust that makes it straightforward to bind routes to functions. When integrating OpenID Connect (OIDC) for authentication, developers often store client secrets or API keys in configuration or pass them between Rocket request handlers and OIDC providers. If these keys are exposed—through logs, error responses, insecure deserialization, or misconfigured routes—they become high-value targets for attackers. API keys used for OIDC integrations typically authorize token exchange endpoints and backend discovery endpoints; leaking them can allow an attacker to impersonate the application, obtain tokens for users, or call downstream services on behalf of the application.
In Rocket, OIDC is commonly implemented via the rocket_okta, rocket_oauth2, or manual OpenID Connect flows using providers like Auth0, Okta, or Google. A typical integration involves a client secret or a private key used to authenticate the client at the provider’s token endpoint. If these credentials are embedded in source code, environment variables that are logged, or routes that return configuration details to unauthenticated clients, an unauthenticated attacker can retrieve the key. For example, a debug route or an improperly secured endpoint that returns the current OIDC configuration can inadvertently expose the client secret. Because Rocket routes are defined at compile time and request handling is asynchronous, any handler that serializes sensitive configuration into responses—intentionally or unintentionally—creates a disclosure path.
Another vector specific to Rocket + OIDC is the interplay between unauthenticated public endpoints and authenticated token endpoints. An attacker can probe the application to locate OIDC discovery documents (.well-known/openid-configuration) and use exposed client identifiers to craft token requests. If the client secret or API key is weak, stored in plaintext, or rotated infrequently, the attacker can complete the OAuth flow and obtain access tokens. This becomes especially dangerous when Rocket applications expose administrative or diagnostic routes without authentication, as these can reveal configuration that includes key material. The framework does not implicitly leak keys, but the application’s structure—combined with incorrect secret management—can create an exposure window that an automated scanner like middleBrick can detect by correlating unauthenticated endpoint behavior with known OIDC configuration patterns.
Consider a Rocket route that returns OIDC metadata for a client-side application:
#[get("/.well-known/openid-configuration")]
fn oidc_config() -> Json<OidcConfig> {
Json(OidcConfig {
issuer: "https://auth.example.com".to_string(),
client_id: "public-client".to_string(),
// Danger: accidentally exposing a client secret in a debug build
client_secret: std::option::Option::Some("super-secret-key".to_string()),
authorization_endpoint: "https://auth.example.com/oauth/authorize".to_string(),
token_endpoint: "https://auth.example.com/oauth/token".to_string(),
})
}
If this route is accessible without authentication and the build configuration is not carefully controlled, the client secret can be retrieved by any unauthenticated user. Even if the route is intended for internal use, a misconfigured Rocket route or a missing guard can make it public. middleBrick detects such exposure by correlating unauthenticated endpoint behavior with patterns that suggest credential leakage in OIDC flows.
Additionally, logging practices in Rocket can inadvertently expose API keys. If the OIDC client initialization or token exchange logs the full configuration or secrets—intentionally for debugging or accidentally through verbose logging—those logs can be accessed by unauthorized users with log read permissions. Rocket’s request guards and fairing systems can intercept and log request and response data; without redaction of sensitive fields, keys can persist in log stores and be exfiltrated.
Finally, the use of environment variables for secrets is common in Rocket deployments, but if those variables are injected into process arguments or printed during panic handling, they can be visible to attackers with limited shell access. The combination of Rocket’s explicit route definitions and OIDC’s reliance on static client credentials amplifies the impact of any accidental exposure. Because API keys in this context function as bearer credentials for token issuance, their compromise can lead to token forgery and unauthorized access to protected resources.
OpenID Connect-Specific Remediation in Rocket — concrete code fixes
Remediation focuses on strict separation of public and sensitive configuration, secure handling of secrets, and ensuring that no credential material is returned to unauthenticated clients. Below are concrete, idiomatic Rocket code examples that implement secure OIDC integration.
1. Keep client secrets out of responses and routes. Never serialize secrets into JSON or return them via any endpoint accessible without authentication.
#[get("/.well-known/openid-configuration")]
fn oidc_config_public() -> Json<OidcConfigPublic> {
Json(OidcConfigPublic {
issuer: "https://auth.example.com".to_string(),
client_id: "public-client".to_string(),
authorization_endpoint: "https://auth.example.com/oauth/authorize".to_string(),
token_endpoint: "https://auth.example.com/oauth/token".to_string(),
// Never include client_secret here
})
}
#[derive(Serialize)]
struct OidcConfigPublic {
issuer: String,
client_id: String,
authorization_endpoint: String,
token_endpoint: String,
}
2. Store secrets securely and access them only in server initialization, not per-request. Use Rocket’s managed state to hold a configuration that is populated at launch without exposing secrets per request.
use rocket::State;
use std::sync::Mutex;
struct OidcSecrets {
client_secret: String,
}
#[rocket::main]
async fn main() {
let secret = std::env::var("OIDC_CLIENT_SECRET")
.expect("OIDC_CLIENT_SECRET must be set");
let secrets = OidcSecrets { client_secret: secret };
rocket::build()
.manage(secrets)
.mount(&Routes::default())
.launch()
.await
.unwrap();
}
#[get("/token-exchange")]
fn token_exchange(secrets: &State<OidcSecrets>) -> Json<Value> {
// Use secrets.client_secret here for confidential client operations
// Do not return secrets in the response
json!({ "status": "ready", "has_secret": secrets.client_secret.is_empty() }
}
3. Redact sensitive fields in logs and avoid logging secrets during token exchange. Configure log filters to exclude secret values and use structured logging that omits key material.
use rocket::request::{Request, RequestGuard, FromRequest};
use rocket::outcome::Outcome;
use std::sync::Arc;
struct SensitiveFilter;
impl<'r> rocket::request::FromRequest<'r> for SensitiveFilter {
type Error = ();
fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
// Example: filter logic to scrub logs
Outcome::Success(SensitiveFilter)
}
}
// Configure logging in Rocket.toml to filter out keys:
// [log]
# filter = "sensitive"
4. Rotate keys regularly and enforce short-lived tokens. Configure the OIDC provider to issue tokens with limited lifetimes and use Rocket-managed state to hold rotated secrets without redeploying code.
#[post("/rotate-secret")]
async fn rotate_secret(secrets: &State<Mutex<OidcSecrets>>) -> Json<Value> {
let new_secret = generate_strong_secret();
let mut guard = secrets.lock().unwrap();
guard.client_secret = new_secret;
json!({ "status": "rotated" })
}
fn generate_strong_secret() -> String {
use rand::Rng;
rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(32)
.map(char::from)
.collect()
}
5. Enforce transport security and validate issuer configuration to prevent token endpoint misuse. Always use HTTPS and validate the OIDC issuer against a known value in request guards.
#[get("/protected")]
fn protected(headers: &Headers) -> Result<Json<Value>, (&'static str, Status)> {
// Validate that requests originate from a trusted issuer by inspecting custom headers or tokens
// Reject requests that do not present a valid authorization context
if headers.get_one("x-issuer").map(|v| v == "https://auth.example.com").unwrap_or(false) {
Ok(Json(json!({ "data": "secure" })))
} else {
Err(("Unauthorized", Status::Unauthorized))
}
}
By applying these patterns—never exposing secrets in routes, managing secrets via state, redacting logs, rotating keys, and enforcing transport and issuer validation—you reduce the risk of API key exposure in Rocket applications using OpenID Connect. These measures align with secure secret handling and help ensure that credentials are not inadvertently disclosed through the application’s public interface.