Skip to main content

authly_flow/
client_credentials_flow.rs

1use authly_core::{AuthError, OAuthToken};
2
3/// Orchestrates the Client Credentials Flow (RFC 6749 Section 4.4).
4///
5/// This flow is used by clients to obtain an access token outside of the context
6/// of a user. This is typically used for client-to-client communication.
7pub struct ClientCredentialsFlow {
8    client_id: String,
9    client_secret: String,
10    token_url: String,
11    http_client: reqwest::Client,
12}
13
14impl ClientCredentialsFlow {
15    /// Creates a new `ClientCredentialsFlow` instance.
16    ///
17    /// # Arguments
18    ///
19    /// * `client_id` - The client ID assigned to the client.
20    /// * `client_secret` - The client secret assigned to the client.
21    /// * `token_url` - The URL of the token endpoint.
22    pub fn new(client_id: String, client_secret: String, token_url: String) -> Self {
23        Self {
24            client_id,
25            client_secret,
26            token_url,
27            http_client: reqwest::Client::new(),
28        }
29    }
30
31    /// Obtains an access token using the client credentials.
32    ///
33    /// # Arguments
34    ///
35    /// * `scopes` - An optional list of scopes to request.
36    ///
37    /// # Returns
38    ///
39    /// A `Result` containing the `OAuthToken` if successful, or an `AuthError` otherwise.
40    pub async fn get_token(&self, scopes: Option<&[&str]>) -> Result<OAuthToken, AuthError> {
41        let mut params = vec![
42            ("grant_type", "client_credentials"),
43            ("client_id", &self.client_id),
44            ("client_secret", &self.client_secret),
45        ];
46
47        let scope_str;
48        if let Some(s) = scopes {
49            scope_str = s.join(" ");
50            params.push(("scope", &scope_str));
51        }
52
53        let response = self
54            .http_client
55            .post(&self.token_url)
56            .header("Accept", "application/json")
57            .form(&params)
58            .send()
59            .await
60            .map_err(|_| AuthError::Network)?;
61
62        if !response.status().is_success() {
63            let error_text = response.text().await.unwrap_or_default();
64            return Err(AuthError::Provider(format!(
65                "Token request failed: {}",
66                error_text
67            )));
68        }
69
70        response
71            .json::<OAuthToken>()
72            .await
73            .map_err(|e| AuthError::Provider(format!("Failed to parse token response: {}", e)))
74    }
75}