rocket_oidc/
client.rs

1use crate::{AddClaims, PronounClaim};
2use jsonwebtoken::*;
3use openidconnect::core::CoreGenderClaim;
4use openidconnect::core::*;
5use reqwest::Url;
6use serde_derive::*;
7use std::str::FromStr;
8
9use crate::CoreClaims;
10use crate::Error;
11use crate::OIDCConfig;
12use serde_json::Value;
13use std::collections::HashMap;
14use std::fmt::Debug;
15
16use crate::token::*;
17use serde::de::DeserializeOwned;
18use std::collections::HashSet;
19use std::path::Path;
20use tokio::{fs::File, io::AsyncReadExt};
21
22use openidconnect::reqwest;
23use openidconnect::*;
24use serde::Serialize;
25
26pub type OpenIDClient<
27    HasDeviceAuthUrl = EndpointNotSet,
28    HasIntrospectionUrl = EndpointNotSet,
29    HasRevocationUrl = EndpointNotSet,
30    HasAuthUrl = EndpointSet,
31    HasTokenUrl = EndpointMaybeSet,
32    HasUserInfoUrl = EndpointMaybeSet,
33> = openidconnect::Client<
34    EmptyAdditionalClaims,
35    CoreAuthDisplay,
36    CoreGenderClaim,
37    CoreJweContentEncryptionAlgorithm,
38    CoreJsonWebKey,
39    CoreAuthPrompt,
40    StandardErrorResponse<CoreErrorResponseType>,
41    CoreTokenResponse,
42    CoreTokenIntrospectionResponse,
43    CoreRevocableToken,
44    CoreRevocationErrorResponse,
45    HasAuthUrl,
46    HasDeviceAuthUrl,
47    HasIntrospectionUrl,
48    HasRevocationUrl,
49    HasTokenUrl,
50    HasUserInfoUrl,
51>;
52
53fn join_url(root: &str, route: &str) -> Result<String, url::ParseError> {
54    let base = Url::parse(root)?;
55    let joined = base.join(route)?;
56    Ok(joined.into())
57}
58
59fn trim_trailing_whitespace(s: &str) -> String {
60    s.trim_end().to_string()
61}
62
63async fn laod_client_secret<P: AsRef<Path>>(
64    secret_file: P,
65) -> Result<ClientSecret, std::io::Error> {
66    let mut file = File::open(secret_file.as_ref()).await?;
67    let mut contents = String::new();
68
69    file.read_to_string(&mut contents).await?;
70    let secret = trim_trailing_whitespace(&contents);
71    #[cfg(debug_assertions)]
72    println!("using secret: {}", secret);
73    Ok(ClientSecret::new(secret))
74}
75
76fn hashset_from<T: std::cmp::Eq + std::hash::Hash>(vals: Vec<T>) -> HashSet<T> {
77    let mut set = HashSet::new();
78    for val in vals.into_iter() {
79        set.insert(val);
80    }
81    set
82}
83
84/// Configuration used internally by the OIDC client to manage static values.
85///
86/// Contains client credentials and metadata loaded from a higher-level `OIDCConfig`.
87#[derive(Debug, Clone, Serialize)]
88pub struct WorkingConfig {
89    client_secret: ClientSecret,
90    client_id: ClientId,
91    issuer_url: IssuerUrl,
92    redirect: String,
93}
94
95impl WorkingConfig {
96    /// Constructs a new `WorkingConfig` from a high-level `OIDCConfig`.
97    ///
98    /// Loads the client secret asynchronously (e.g., from a file or secure vault).
99    ///
100    /// # Arguments
101    /// * `config` - The high-level configuration containing static strings and secret references.
102    ///
103    /// # Returns
104    /// * `Ok(WorkingConfig)` on success.
105    /// * `Err` if loading or parsing fails.
106    pub async fn from_oidc_config(config: &OIDCConfig) -> Result<Self, Box<dyn std::error::Error>> {
107        let client_id = config.client_id.clone();
108        let issuer_url = config.issuer_url.clone();
109
110        let client_id = ClientId::new(client_id);
111        let client_secret = laod_client_secret(&config.client_secret).await?;
112        let issuer_url = IssuerUrl::new(issuer_url)?;
113
114        Ok(Self {
115            client_id,
116            client_secret,
117            issuer_url,
118            redirect: config.redirect.clone(),
119        })
120    }
121}
122
123/// Identifier for a public key used in token validation.
124///
125/// Combines the issuer and algorithm to uniquely reference a key in a multi-issuer or multi-algorithm environment.
126/// This is not that same as what's used within openidconnect crate, just used by this crates validator.
127#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
128pub struct KeyID {
129    issuer: String,
130    alg: String,
131}
132
133impl KeyID {
134    /// Creates a new `KeyID` from issuer and algorithm strings.
135    ///
136    /// # Arguments
137    /// * `issuer` - The key issuer.
138    /// * `alg` - The signing algorithm.
139    pub fn new(issuer: &str, alg: &str) -> Self {
140        KeyID {
141            issuer: issuer.to_string(),
142            alg: alg.to_string(),
143        }
144    }
145}
146
147/// Represents a manually configured public key endpoint for token validation.
148///
149/// Contains validation rules and the decoding key used to verify signatures.
150#[derive(Clone, Debug)]
151pub struct Endpoint {
152    validation: Validation,
153    pubkey: DecodingKey,
154}
155
156impl Endpoint {
157    /// Creates a new `Endpoint` with the given validation rules and decoding key.
158    ///
159    /// # Arguments
160    /// * `validation` - Validation settings (audience, issuer, leeway, etc.).
161    /// * `pubkey` - The public key used to verify signatures.
162    pub fn new(validation: Validation, pubkey: DecodingKey) -> Self {
163        Self { validation, pubkey }
164    }
165}
166
167/// A helper type for validating JSON Web Tokens (JWTs) against multiple issuers and algorithms.
168///
169/// `Validator` manages a collection of public keys and associated validation rules
170/// (wrapped in `Endpoint` structs) that can be used to decode and verify tokens.
171/// It supports loading keys dynamically from JWKS endpoints or inserting them manually.
172///
173/// Typically used when your application needs to accept tokens from multiple providers
174/// (e.g., multiple OpenID Connect issuers) or support multiple signing algorithms.
175#[derive(Clone, Debug)]
176pub struct Validator {
177    // A mapping from composite key identifiers (`KeyID`) — usually derived from issuer and algorithm —
178    // to the corresponding validation endpoint (`Endpoint`) containing the decoding key and validation rules.
179    pubkeys: HashMap<KeyID, Endpoint>,
180
181    // Default issuer URL, used by legacy or simplified decoding methods.
182    // Note: this may not always be correct if your validator handles multiple issuers.
183    default_iss: String,
184}
185
186fn parse_jwks(
187    issuer: &str,
188    jwks_json: &str,
189    mut validation: Validation,
190) -> Result<HashMap<KeyID, Endpoint>, Box<dyn std::error::Error>> {
191    let jwks: Value = serde_json::from_str(jwks_json)?;
192    let keys_array = jwks["keys"]
193        .as_array()
194        .ok_or("JWKS does not contain a 'keys' array")?;
195
196    let mut keys = HashMap::new();
197
198    for jwk in keys_array {
199        let alg = jwk["alg"].as_str().ok_or("Missing 'alg' in JWK")?;
200        let kid = jwk["kid"].as_str().unwrap_or("default");
201
202        let decoding_key = match alg {
203            "RS256" | "RS384" | "RS512" => {
204                let n = jwk["n"].as_str().ok_or("Missing 'n' in RSA JWK")?;
205                let e = jwk["e"].as_str().ok_or("Missing 'e' in RSA JWK")?;
206                DecodingKey::from_rsa_components(n, e)?
207            }
208
209            "ES256" | "ES384" | "ES512" => {
210                let x = jwk["x"].as_str().ok_or("Missing 'x' in EC JWK")?;
211                let y = jwk["y"].as_str().ok_or("Missing 'y' in EC JWK")?;
212                DecodingKey::from_ec_components(x, y)?
213            }
214
215            "HS256" | "HS384" | "HS512" => {
216                let k = jwk["k"].as_str().ok_or("Missing 'k' in symmetric JWK")?;
217                DecodingKey::from_base64_secret(k)?
218            }
219
220            other => {
221                eprintln!("Unsupported algorithm: {}", other);
222                continue; // skip this key
223            }
224        };
225
226        let key_id = KeyID::new(issuer, alg);
227        validation.algorithms = vec![Algorithm::from_str(alg)?];
228        keys.insert(key_id, Endpoint::new(validation.clone(), decoding_key));
229    }
230
231    Ok(keys)
232}
233
234impl Validator {
235    /// Creates a new `Validator` from a single public key.
236    ///
237    /// This is useful when you already have a known key (for example, configured statically)
238    /// and want to build a validator around it.
239    ///
240    /// * `url` - Issuer URL.
241    /// * `audiance` - Expected audience claim (usually your client ID).
242    /// * `algorithm` - Signing algorithm (e.g., "RS256").
243    /// * `public_key` - Decoding key used to verify signatures.
244    pub fn from_pubkey(
245        url: String,
246        audiance: String,
247        algorithm: String,
248        public_key: DecodingKey,
249    ) -> Result<Self, Box<dyn std::error::Error>> {
250        let pubkeys = HashMap::new();
251        let algo = Algorithm::from_str(&algorithm)?;
252        //let validation = Validation::new(algo);
253        //validation.insecure_disable_signature_validation();
254        let mut validator = Self {
255            pubkeys,
256            default_iss: url.clone(),
257        };
258
259        validator.insert_pubkey(url, audiance, algorithm, public_key)?;
260        Ok(validator)
261    }
262
263    /// Creates a new `Validator` from an RSA PEM encoded public key.
264    ///
265    /// This is a convenience wrapper around `from_pubkey` that accepts a PEM string
266    /// (PKCS#1 / PKCS#8 public key) and builds the `DecodingKey` for you.
267    ///
268    /// * `url` - Issuer URL (used to construct the KeyID).
269    /// * `audiance` - Expected audience claim.
270    /// * `algorithm` - Signing algorithm (e.g., "RS256").
271    /// * `pem` - RSA public key in PEM format.
272    pub fn from_rsa_pem(
273        url: String,
274        audiance: String,
275        algorithm: String,
276        pem: &str,
277    ) -> Result<Self, Box<dyn std::error::Error>> {
278        let decoding_key = DecodingKey::from_rsa_pem(pem.as_bytes())
279            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
280        Self::from_pubkey(url, audiance, algorithm, decoding_key)
281    }
282
283    /// Returns a sorted list of unique algorithms supported for the given issuer,
284    /// based on the pubkeys map.
285    pub fn get_supported_algorithms_for_issuer(&self, issuer: &str) -> Option<Vec<String>> {
286        let mut algs: Vec<String> = self
287            .pubkeys
288            .keys()
289            .filter(|key_id| key_id.issuer == issuer)
290            .map(|key_id| key_id.alg.clone())
291            .collect();
292
293        // Remove duplicates & sort
294        algs.sort();
295        algs.dedup();
296
297        if algs.is_empty() { None } else { Some(algs) }
298    }
299
300    pub fn empty() -> Self {
301        Self {
302            pubkeys: HashMap::new(),
303            default_iss: "".to_string(),
304        }
305    }
306
307    /// Loads public keys dynamically from a JWKS endpoint discovered from provider metadata.
308    ///
309    /// Fetches the JWKS, parses it, and builds validation rules for each key.
310    ///
311    /// * `validation` - Template validation rules to apply for each key.
312    /// * `provider_metadata` - OpenID Connect provider metadata (must include JWKS URI).
313    /// * `issuer_url` - The expected issuer URL.
314    pub async fn new(
315        validation: Validation,
316        provider_metadata: &CoreProviderMetadata,
317        issuer_url: String,
318    ) -> Result<Self, Box<dyn std::error::Error>> {
319        let jwks_uri = provider_metadata.jwks_uri().to_string();
320
321        let jwks_json = reqwest::get(jwks_uri).await?.text().await?;
322        let keys = parse_jwks(&issuer_url, &jwks_json, validation.clone())?;
323        Ok(Self {
324            pubkeys: keys,
325            default_iss: issuer_url,
326        })
327    }
328
329    fn default_validation(
330        url: &str,
331        audiance: &str,
332        algorithm: &str,
333    ) -> Result<Validation, Box<dyn std::error::Error>> {
334        let algo = Algorithm::from_str(&algorithm)?;
335        let mut validation = Validation::new(algo);
336        //validation.insecure_disable_signature_validation();
337        {
338            validation.leeway = 100; // Optionally, allow some leeway
339            validation.validate_exp = true;
340            validation.validate_aud = true;
341            validation.validate_nbf = true;
342            validation.aud = Some(hashset_from(vec![audiance.to_string()])); // The audience should match your client ID
343            validation.iss = Some(hashset_from(vec![url.to_string()])); // Validate the issuer
344            validation.algorithms = vec![algo];
345        };
346
347        Ok(validation)
348    }
349
350    /// Extends the validator by dynamically discovering and importing public keys (JWKS)
351    /// from the OpenID Connect (OIDC) discovery endpoint of the given issuer.
352    ///
353    /// This method performs the following steps:
354    /// 1. Initializes an HTTP client with redirect-following disabled for security reasons.
355    /// 2. Fetches the OpenID Connect provider metadata from the issuer's well-known discovery endpoint.
356    /// 3. Retrieves the JWKS (JSON Web Key Set) URI from the provider metadata.
357    /// 4. Downloads the JWKS document and parses the keys.
358    /// 5. Inserts the discovered keys into the validator's `pubkeys` map, associating them with the issuer.
359    ///
360    /// # Parameters
361    /// - `issuer_url`: The base URL of the OIDC issuer (e.g., `https://accounts.example.com`).
362    /// - `validation`: The validation params used for this endpoint (make sure iss, aud, alg are set correctly)
363    /// # Returns
364    /// - `Ok(())` if the keys were successfully fetched and added.
365    /// - `Err(Box<dyn std::error::Error>)` if any network, parsing, or validation step fails.
366    ///
367    /// # Errors
368    /// Returns an error if:
369    /// - The HTTP client could not be created.
370    /// - The issuer URL is invalid.
371    /// - The provider metadata discovery fails.
372    /// - The JWKS document cannot be fetched or parsed.
373    ///
374    /// # Security
375    /// - Redirects are explicitly disabled to prevent SSRF attacks when contacting the discovery endpoint.
376    ///
377    /// # Example
378    /// ```ignore
379    /// use rocket_oidc::client::Validator;
380    /// let mut validator = Validator::empty();
381    /// validator.extend_from_oidc("https://accounts.example.com").await?;
382    /// ```
383    pub async fn extend_from_oidc(
384        &mut self,
385        issuer_url: &str,
386        audiance: &str,
387        default_algorithm: &str,
388    ) -> Result<(), Box<dyn std::error::Error>> {
389        let http_client = match reqwest::ClientBuilder::new()
390            // Following redirects opens the client up to SSRF vulnerabilities.
391            .redirect(reqwest::redirect::Policy::none())
392            .build()
393        {
394            Ok(client) => client,
395            Err(e) => return Err(Box::new(e)),
396        };
397
398        let provider_metadata = match CoreProviderMetadata::discover_async(
399            IssuerUrl::new(issuer_url.to_string())?,
400            &http_client,
401        )
402        .await
403        {
404            Ok(provider_metadata) => provider_metadata,
405            Err(e) => return Err(Box::new(e)),
406        };
407        let validation = Self::default_validation(issuer_url, audiance, default_algorithm)?;
408
409        let jwks_uri = provider_metadata.jwks_uri().to_string();
410        let jwks_json = reqwest::get(jwks_uri).await?.text().await?;
411        let keys = parse_jwks(&issuer_url, &jwks_json, validation.clone())?;
412        for (key, value) in keys.into_iter() {
413            self.pubkeys.insert(key, value);
414        }
415        Ok(())
416    }
417
418    /// Extends the validator by parsing and adding public keys from a JWKS JSON document
419    /// associated with the given issuer.
420    ///
421    /// # Parameters
422    /// - `issuer_url`: The base URL of the OIDC issuer (e.g., `https://accounts.example.com`).
423    /// - `jwks_json`: The raw JWKS JSON string.
424    ///
425    /// # Returns
426    /// - `Ok(())` if the keys were successfully parsed and added.
427    /// - `Err(crate::Error)` if parsing fails.
428    ///
429    /// # Example
430    /// ```ignore
431    /// let mut validator = Validator::empty();
432    /// let jwks_json = std::fs::read_to_string("keys.json")?;
433    /// validator.extend_from_jwks("https://accounts.example.com", &jwks_json)?;
434    /// ```
435    pub fn extend_from_jwks(
436        &mut self,
437        issuer_url: &str,
438        jwks_json: &str,
439        validation: Validation,
440    ) -> Result<(), Box<dyn std::error::Error>> {
441        // Parse the keys, associating them with the issuer and current validation config
442        let keys = parse_jwks(issuer_url, jwks_json, validation.clone())?;
443
444        // Insert them into the validator's pubkeys map
445        for (key_id, endpoint) in keys {
446            self.pubkeys.insert(key_id, endpoint);
447        }
448
449        Ok(())
450    }
451
452    /// Inserts a new validation endpoint directly by its `KeyID`.
453    ///
454    /// Useful when you already constructed an `Endpoint` yourself.
455    pub fn insert_endpoint(&mut self, keyid: KeyID, endpoint: Endpoint) {
456        self.pubkeys.insert(keyid, endpoint);
457    }
458
459    /// Inserts a new public key and automatically builds its validation rules.
460    ///
461    /// * `url` - Issuer URL.
462    /// * `audiance` - Expected audience claim.
463    /// * `algorithm` - Signing algorithm.
464    /// * `public_key` - Decoding key.
465    pub fn insert_pubkey(
466        &mut self,
467        url: String,
468        audiance: String,
469        algorithm: String,
470        public_key: DecodingKey,
471    ) -> Result<(), Box<dyn std::error::Error>> {
472        let algo = Algorithm::from_str(&algorithm)?;
473        let validation = Self::default_validation(&url, &audiance, &algorithm)?;
474
475        let keyid = KeyID::new(&url, &algorithm);
476        self.pubkeys
477            .insert(keyid, Endpoint::new(validation, public_key));
478        Ok(())
479    }
480
481    /// Decodes and validates an access token for a specific issuer and algorithm.
482    ///
483    /// * `issuer` - Issuer URL.
484    /// * `algorithm` - Signing algorithm (e.g., "RS256").
485    /// * `access_token` - The JWT string to decode.
486    ///
487    /// Returns the token's claims if valid, or an error otherwise.
488    pub fn decode_with_iss_alg<
489        T: Serialize + Debug + DeserializeOwned + std::marker::Send + CoreClaims + Clone,
490    >(
491        &self,
492        issuer: &str,
493        algorithm: &str,
494        access_token: &str,
495    ) -> Result<TokenData<T>, crate::Error> {
496        let keyid = KeyID::new(issuer, algorithm);
497        if let Some(endpoint) = self.pubkeys.get(&keyid) {
498            #[cfg(debug_assertions)]
499            {
500                let mut emptyvalidation = Validation::new(Algorithm::from_str(algorithm)?);
501                emptyvalidation.validate_aud = false;
502                emptyvalidation.validate_exp = false;
503                emptyvalidation.validate_nbf = false;
504                match jsonwebtoken::decode::<serde_json::Value>(
505                    access_token,
506                    &endpoint.pubkey,
507                    &emptyvalidation,
508                ) {
509                    Ok(data) => {
510                        eprintln!("DEBUG: Unvalidated token claims: {:#?}", data.claims);
511                    }
512                    Err(e) => {
513                        eprintln!("DEBUG: Failed to decode unvalidated token: {:?}", e);
514                    }
515                }
516            }
517            Ok(decode::<T>(
518                access_token,
519                &endpoint.pubkey,
520                &endpoint.validation,
521            )?)
522        } else {
523            Err(Error::PubKeyNotFound(keyid))
524        }
525    }
526
527    /// Decodes and validates an access token using the default issuer and a default algorithm ("RS256").
528    ///
529    /// ⚠️ **Deprecated:** May not be correct if you handle multiple issuers or algorithms.
530    #[deprecated]
531    pub fn decode<
532        T: Serialize + Debug + DeserializeOwned + std::marker::Send + CoreClaims + Clone,
533    >(
534        &self,
535        access_token: &str,
536    ) -> Result<TokenData<T>, crate::Error> {
537        self.decode_with_iss_alg::<T>(&self.default_iss, "RS256", access_token)
538    }
539}
540
541/// A high-level OpenID Connect (OIDC) client abstraction for performing common flows:
542/// - Discovering provider metadata
543/// - Exchanging authorization codes for tokens
544/// - Fetching user information
545/// - Performing token exchange
546///
547/// Internally, `OIDCClient` combines:
548/// - An OpenID Connect client (`OpenIDClient`)
549/// - A reqwest HTTP client (`reqwest::Client`)
550/// - Local configuration (`WorkingConfig`)
551///
552/// This design allows dynamic discovery from OIDC configuration,
553/// while keeping a ready-to-use validator for verifying ID tokens or access tokens.
554#[derive(Debug, Clone)]
555pub struct OIDCClient {
556    // The OpenID Connect client instance, created from discovered provider metadata.
557    pub client: OpenIDClient,
558
559    // The reqwest HTTP client used for token and userinfo requests.
560    reqwest_client: reqwest::Client,
561
562    // Local, working configuration values (e.g., client ID, secret, redirect URL, issuer).
563    config: WorkingConfig,
564}
565
566impl OIDCClient {
567    /// Creates a new `OIDCClient` by dynamically discovering the provider metadata
568    /// and preparing a `Validator` to verify tokens.
569    ///
570    /// This method:
571    /// - Builds a safe reqwest HTTP client (with redirect following disabled).
572    /// - Discovers the OpenID provider metadata from the issuer URL.
573    /// - Constructs default validation rules (audience, issuer, leeway).
574    /// - Loads the JWKS keys into a `Validator`.
575    /// - Initializes the OpenID Connect client with the discovered metadata.
576    ///
577    /// # Arguments
578    /// * `config` - High-level OIDC configuration.
579    ///
580    /// # Returns
581    /// A tuple of:
582    /// - `OIDCClient` (for performing login and userinfo flows)
583    /// - `Validator` (for verifying ID tokens or access tokens)
584    ///
585    /// # Errors
586    /// Returns an error if discovery fails, the JWKS endpoint cannot be fetched,
587    /// or if the HTTP client cannot be built.
588    pub async fn from_oidc_config(
589        config: &OIDCConfig,
590    ) -> Result<(Self, Validator), Box<dyn std::error::Error>> {
591        let config = WorkingConfig::from_oidc_config(config).await?;
592
593        let http_client = match reqwest::ClientBuilder::new()
594            // Following redirects opens the client up to SSRF vulnerabilities.
595            .redirect(reqwest::redirect::Policy::none())
596            .build()
597        {
598            Ok(client) => client,
599            Err(e) => return Err(Box::new(e)),
600        };
601
602        let provider_metadata =
603            match CoreProviderMetadata::discover_async(config.issuer_url.clone(), &http_client)
604                .await
605            {
606                Ok(provider_metadata) => provider_metadata,
607                Err(e) => return Err(Box::new(e)),
608            };
609
610        // Decode and verify the JWT
611        let mut validation = Validation::new(Algorithm::RS256);
612        //validation.insecure_disable_signature_validation();
613        {
614            validation.leeway = 100; // Optionally, allow some leeway
615            validation.validate_exp = true;
616            validation.validate_aud = true;
617            validation.validate_nbf = true;
618            validation.aud = Some(hashset_from(vec!["account".to_string()])); // The audience should match your client ID
619            validation.iss = Some(hashset_from(vec![config.issuer_url.to_string()])); // Validate the issuer
620        };
621
622        let validator = Validator::new(
623            validation,
624            &provider_metadata,
625            config.issuer_url.to_string(),
626        )
627        .await?;
628
629        // Set up the config for the GitLab OAuth2 process.
630        let client = CoreClient::from_provider_metadata(
631            provider_metadata,
632            config.client_id.clone(),
633            Some(config.client_secret.clone()),
634        )
635        // This example will be running its own server at localhost:8080.
636        // See below for the server implementation.
637        .set_redirect_uri(
638            RedirectUrl::new(join_url(&config.redirect, "/auth/callback/").unwrap())
639                .unwrap_or_else(|_err| {
640                    unreachable!();
641                }),
642        );
643
644        Ok((
645            Self {
646                client,
647                config,
648                reqwest_client: http_client,
649            },
650            validator,
651        ))
652    }
653
654    /// Fetches user information from the provider's UserInfo endpoint.
655    ///
656    /// # Arguments
657    /// * `access_token` - The access token obtained after login.
658    /// * `subject` - Optionally, the subject (user ID) to query.
659    ///
660    /// # Returns
661    /// The claims returned by the UserInfo endpoint.
662    ///
663    /// # Errors
664    /// Returns an error if the request fails or the response is invalid.
665    pub async fn user_info(
666        &self,
667        access_token: AccessToken,
668        subject: Option<SubjectIdentifier>,
669    ) -> Result<
670        UserInfoClaims<AddClaims, PronounClaim>,
671        UserInfoError<openidconnect::HttpClientError<reqwest::Error>>,
672    > {
673        self.client
674            .user_info(
675                access_token, // AccessToken::new(access_token.value().to_string())
676                subject,      //Some(SubjectIdentifier::new(data.claims.subject().to_string()))
677            )
678            .unwrap()
679            .request_async(&self.reqwest_client)
680            .await
681    }
682
683    /// Exchanges an authorization code (received after user login) for a token response.
684    ///
685    /// # Arguments
686    /// * `code` - The authorization code.
687    ///
688    /// # Returns
689    /// The token response, including access token and optionally ID token or refresh token.
690    ///
691    /// # Errors
692    /// Returns an error if the token request fails or the response is invalid.
693    pub async fn exchange_code(
694        &self,
695        code: AuthorizationCode,
696    ) -> Result<CoreTokenResponse, crate::Error> {
697        Ok(self
698            .client
699            .exchange_code(code)?
700            .request_async(&self.reqwest_client)
701            .await?)
702    }
703
704    /// Performs OAuth2 token exchange to obtain a token scoped for a different audience.
705    ///
706    /// # Arguments
707    /// * `subject_token` - The current access token or ID token.
708    /// * `audience` - The target audience for the exchanged token.
709    ///
710    /// # Returns
711    /// The token exchange response, containing the new token.
712    ///
713    /// # Errors
714    /// Returns a reqwest error if the request fails.
715    ///
716    /// # Note
717    /// I haven't tested this in a full flow.
718    pub async fn exchange_token_for_audience(
719        &self,
720        subject_token: &str,
721        audience: &str,
722    ) -> Result<TokenExchangeResponse, reqwest::Error> {
723        crate::token::perform_token_exchange(
724            self.client.token_uri().unwrap().as_str(),
725            self.config.client_id.as_str(),
726            self.config.client_secret.secret().as_str(),
727            subject_token,
728            audience,
729        )
730        .await
731    }
732
733    /// Like `from_oidc_config`, but allows the caller to provide a custom `Validation` template.
734    ///
735    /// This can be used to:
736    /// - Disable signature verification for testing.
737    /// - Adjust expiration leeway, audience, issuer, etc.
738    /// - Support different algorithms.
739    pub async fn from_oidc_config_with_validation(
740        config: &OIDCConfig,
741        custom_validation: Validation,
742    ) -> Result<(Self, Validator), Box<dyn std::error::Error>> {
743        let config = WorkingConfig::from_oidc_config(config).await?;
744        let http_client = reqwest::ClientBuilder::new()
745            .redirect(reqwest::redirect::Policy::none())
746            .build()?;
747
748        let provider_metadata =
749            CoreProviderMetadata::discover_async(config.issuer_url.clone(), &http_client).await?;
750
751        let validator = Validator::new(
752            custom_validation,
753            &provider_metadata,
754            config.issuer_url.to_string(),
755        )
756        .await?;
757
758        let client = CoreClient::from_provider_metadata(
759            provider_metadata,
760            config.client_id.clone(),
761            Some(config.client_secret.clone()),
762        )
763        .set_redirect_uri(
764            RedirectUrl::new(join_url(&config.redirect, "/auth/callback/").unwrap()).unwrap(),
765        );
766
767        Ok((
768            Self {
769                client,
770                config,
771                reqwest_client: http_client,
772            },
773            validator,
774        ))
775    }
776}
777
778/// this struct provides extra data stored in a cookie that's used to identify
779/// which issuer provided the json web token so it can be properly constructed.
780#[derive(Debug, Clone, Serialize, Deserialize)]
781pub struct IssuerData {
782    pub issuer: String,
783    pub algorithm: String,
784}