jwk_simple/jwks/remote.rs
1//! Remote JWKS fetching without caching.
2//!
3//! This module provides [`RemoteKeySet`], which fetches keys from an HTTP endpoint
4//! on every request. For production use, consider wrapping with [`CachedKeySet`](super::CachedKeySet).
5
6use std::time::Duration;
7
8use crate::error::Result;
9use crate::jwk::Key;
10
11use super::{KeySet, KeySource};
12
13/// Default timeout for HTTP requests (30 seconds).
14pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
15
16/// A JWKS source that fetches from an HTTP endpoint on every request.
17///
18/// This implementation does **not** cache keys. Every call to [`get_key`](KeySource::get_key)
19/// or [`get_keyset`](KeySource::get_keyset) will make an HTTP request.
20///
21/// For production use with high request volumes, wrap this in [`CachedKeySet`](super::CachedKeySet):
22///
23/// ```ignore
24/// use jwk_simple::{RemoteKeySet, CachedKeySet};
25/// use std::time::Duration;
26///
27/// let remote = RemoteKeySet::new("https://example.com/.well-known/jwks.json");
28/// let cached = CachedKeySet::new(remote, Duration::from_secs(300));
29/// ```
30///
31/// # Examples
32///
33/// ```ignore
34/// use jwk_simple::{KeySource, RemoteKeySet};
35///
36/// let jwks = RemoteKeySet::new("https://example.com/.well-known/jwks.json");
37/// let key = jwks.get_key("my-key-id").await?;
38/// ```
39///
40/// # Custom HTTP Client
41///
42/// You can provide a custom [`reqwest::Client`] for full control over HTTP behavior:
43///
44/// ```ignore
45/// use jwk_simple::RemoteKeySet;
46/// use std::time::Duration;
47///
48/// let client = reqwest::Client::builder()
49/// .timeout(Duration::from_secs(10))
50/// .user_agent("my-app/1.0")
51/// .build()
52/// .unwrap();
53///
54/// let jwks = RemoteKeySet::new_with_client(
55/// "https://example.com/.well-known/jwks.json",
56/// client,
57/// );
58/// ```
59#[derive(Debug, Clone)]
60pub struct RemoteKeySet {
61 url: String,
62 client: reqwest::Client,
63}
64
65impl RemoteKeySet {
66 /// Creates a new `RemoteKeySet` from a URL.
67 ///
68 /// Uses a default HTTP client with a 30-second timeout. To customize the client,
69 /// use [`new_with_client`](Self::new_with_client).
70 ///
71 /// # Arguments
72 ///
73 /// * `url` - The JWKS endpoint URL.
74 pub fn new(url: impl Into<String>) -> Self {
75 let client = reqwest::Client::builder()
76 .timeout(DEFAULT_TIMEOUT)
77 .build()
78 .expect("failed to build default HTTP client");
79
80 Self {
81 url: url.into(),
82 client,
83 }
84 }
85
86 /// Creates a new `RemoteKeySet` with a custom HTTP client.
87 ///
88 /// Use this to configure custom timeouts, headers, proxies, TLS settings, etc.
89 ///
90 /// # Arguments
91 ///
92 /// * `url` - The JWKS endpoint URL.
93 /// * `client` - A configured [`reqwest::Client`].
94 ///
95 /// # Examples
96 ///
97 /// ```ignore
98 /// use jwk_simple::RemoteKeySet;
99 /// use std::time::Duration;
100 ///
101 /// let client = reqwest::Client::builder()
102 /// .timeout(Duration::from_secs(10))
103 /// .build()
104 /// .unwrap();
105 ///
106 /// let jwks = RemoteKeySet::new_with_client(
107 /// "https://example.com/.well-known/jwks.json",
108 /// client,
109 /// );
110 /// ```
111 pub fn new_with_client(url: impl Into<String>, client: reqwest::Client) -> Self {
112 Self {
113 url: url.into(),
114 client,
115 }
116 }
117
118 /// Fetches the JWKS from the remote endpoint.
119 async fn fetch(&self) -> Result<KeySet> {
120 let response = self.client.get(&self.url).send().await?;
121 let json = response.text().await?;
122
123 Ok(serde_json::from_str::<KeySet>(&json)?)
124 }
125}
126
127#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
128#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
129impl KeySource for RemoteKeySet {
130 async fn get_key(&self, kid: &str) -> Result<Option<Key>> {
131 let keyset = self.fetch().await?;
132
133 Ok(keyset.find_by_kid(kid).cloned())
134 }
135
136 async fn get_keyset(&self) -> Result<KeySet> {
137 self.fetch().await
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_remote_jwks_new() {
147 let _jwks = RemoteKeySet::new("https://example.com/jwks");
148 }
149
150 #[test]
151 fn test_remote_jwks_new_with_client() {
152 let client = reqwest::Client::builder()
153 .timeout(Duration::from_secs(10))
154 .build()
155 .unwrap();
156
157 let _jwks = RemoteKeySet::new_with_client("https://example.com/jwks", client);
158 }
159}