Exchange client credentials for an access token (OAuth 2.0 Client Credentials flow). Sends a `POST` to `token_url` with: ```text grant_type=client_credentials&client_id=...&client_secret=...&scope=... ``` Returns the raw `access_token` string on success.
(
token_url: &str,
client_id: &str,
client_secret: &str,
scopes: &[String],
)
| 27 | /// |
| 28 | /// Returns the raw `access_token` string on success. |
| 29 | pub async fn exchange_client_credentials( |
| 30 | token_url: &str, |
| 31 | client_id: &str, |
| 32 | client_secret: &str, |
| 33 | scopes: &[String], |
| 34 | ) -> Result<String> { |
| 35 | let client = build_reqwest_client(None, None) |
| 36 | .context("Failed to build HTTP client for OAuth token exchange")?; |
| 37 | |
| 38 | // Build application/x-www-form-urlencoded body |
| 39 | let scope_str = scopes.join(" "); |
| 40 | let params = [ |
| 41 | ("grant_type", "client_credentials"), |
| 42 | ("client_id", client_id), |
| 43 | ("client_secret", client_secret), |
| 44 | ("scope", &scope_str), |
| 45 | ]; |
| 46 | |
| 47 | let response = client |
| 48 | .post(token_url) |
| 49 | .form(¶ms) |
| 50 | .send() |
| 51 | .await |
| 52 | .with_context(|| format!("OAuth token request to {} failed", token_url))?; |
| 53 | |
| 54 | if !response.status().is_success() { |
| 55 | let status = response.status(); |
| 56 | let body = response.text().await.unwrap_or_default(); |
| 57 | return Err(anyhow!( |
| 58 | "OAuth token exchange failed at {} (HTTP {}): {}", |
| 59 | token_url, |
| 60 | status, |
| 61 | body |
| 62 | )); |
| 63 | } |
| 64 | |
| 65 | let token_resp: TokenResponse = response |
| 66 | .json() |
| 67 | .await |
| 68 | .context("Failed to parse OAuth token response")?; |
| 69 | |
| 70 | Ok(token_resp.access_token) |
| 71 | } |
| 72 | |
| 73 | // ============================================================================ |
| 74 | // Tests |