use std::time::Duration;
use crate::core::ClerkError;
const DEFAULT_BACKEND_API_BASE_URL: &str = "https://api.clerk.com/v1";
pub(crate) const DEFAULT_JWKS_CACHE_TTL: Duration = Duration::from_secs(60 * 60);
pub(crate) const DEFAULT_UNKNOWN_KID_REFRESH_INTERVAL: Duration = Duration::from_secs(60 * 5);
pub(crate) const DEFAULT_JWKS_FAILURE_BACKOFF: Duration = Duration::from_secs(10);
pub(crate) const DEFAULT_JWKS_MAX_STALE: Duration = Duration::from_secs(60 * 60 * 24);
pub(crate) const DEFAULT_CLOCK_SKEW: Duration = Duration::from_secs(5);
#[derive(Clone)]
pub struct ClerkAuthLayerConfig {
pub(crate) secret_key: String,
backend_api_base_url: String,
allow_insecure_backend_api_base_url: bool,
pub(crate) authorized_parties: Vec<String>,
pub(crate) audiences: Vec<String>,
pub(crate) issuers: Vec<String>,
pub(crate) clock_skew: Duration,
pub(crate) require_session_id: bool,
}
impl ClerkAuthLayerConfig {
pub fn new(secret_key: impl Into<String>) -> Self {
Self {
secret_key: secret_key.into(),
backend_api_base_url: DEFAULT_BACKEND_API_BASE_URL.into(),
allow_insecure_backend_api_base_url: false,
authorized_parties: vec![],
audiences: vec![],
issuers: vec![],
clock_skew: DEFAULT_CLOCK_SKEW,
require_session_id: true,
}
}
pub fn from_env() -> Result<Self, ClerkError> {
let secret_key = std::env::var("CLERK_SECRET_KEY").map_err(|_| {
ClerkError::InvalidConfig("missing CLERK_SECRET_KEY environment variable".into())
})?;
Ok(Self::new(secret_key))
}
pub fn with_backend_api_base_url(mut self, url: impl Into<String>) -> Self {
self.backend_api_base_url = url.into();
self.allow_insecure_backend_api_base_url = false;
self
}
pub fn with_insecure_backend_api_base_url(mut self, url: impl Into<String>) -> Self {
let url = url.into();
tracing::warn!(
url = %url,
"Clerk backend API base URL configured over insecure HTTP: JWKS signing keys can be tampered with in transit. Use with_backend_api_base_url with an https:// URL outside of local development and tests."
);
self.backend_api_base_url = url;
self.allow_insecure_backend_api_base_url = true;
self
}
pub fn add_authorized_party(mut self, party: impl Into<String>) -> Self {
self.authorized_parties.push(party.into());
self
}
pub fn with_authorized_parties(
mut self,
parties: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.authorized_parties = parties.into_iter().map(Into::into).collect();
self
}
pub fn add_audience(mut self, audience: impl Into<String>) -> Self {
self.audiences.push(audience.into());
self
}
pub fn with_audiences(
mut self,
audiences: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.audiences = audiences.into_iter().map(Into::into).collect();
self
}
pub fn add_issuer(mut self, issuer: impl Into<String>) -> Self {
self.issuers.push(issuer.into());
self
}
pub fn with_issuers(mut self, issuers: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.issuers = issuers.into_iter().map(Into::into).collect();
self
}
pub fn with_clock_skew(mut self, clock_skew: Duration) -> Self {
self.clock_skew = clock_skew;
self
}
pub fn allow_non_session_tokens(mut self) -> Self {
self.require_session_id = false;
self
}
pub(crate) fn jwks_url(&self) -> Result<reqwest::Url, ClerkError> {
let jwks_url = format!("{}/jwks", self.backend_api_base_url.trim_end_matches('/'));
let url = reqwest::Url::parse(&jwks_url).map_err(|error| {
ClerkError::InvalidConfig(format!("invalid Clerk Backend API base URL: {error}"))
})?;
if url.scheme() != "https" && !self.allow_insecure_backend_api_base_url {
return Err(ClerkError::InvalidConfig(
"Clerk Backend API base URL must use https; use with_insecure_backend_api_base_url only for local tests".into(),
));
}
Ok(url)
}
}
impl std::fmt::Debug for ClerkAuthLayerConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClerkAuthLayerConfig")
.field("secret_key", &"<redacted>")
.field("backend_api_base_url", &self.backend_api_base_url)
.field(
"allow_insecure_backend_api_base_url",
&self.allow_insecure_backend_api_base_url,
)
.field("authorized_parties", &self.authorized_parties)
.field("audiences", &self.audiences)
.field("issuers", &self.issuers)
.field("clock_skew", &self.clock_skew)
.field("require_session_id", &self.require_session_id)
.finish()
}
}