azure_identity/
refresh_token.rs
1use azure_core::{
4 auth::Secret,
5 content_type,
6 error::{Error, ErrorKind, ResultExt},
7 headers, HttpClient, Request, Url,
8};
9use azure_core::{from_json, Method};
10use serde::Deserialize;
11use std::fmt;
12use std::sync::Arc;
13use url::form_urlencoded;
14
15pub async fn exchange(
17 http_client: Arc<dyn HttpClient>,
18 tenant_id: &str,
19 client_id: &str,
20 client_secret: Option<&str>,
21 refresh_token: &Secret,
22) -> azure_core::Result<RefreshTokenResponse> {
23 let encoded = {
24 let mut encoded = &mut form_urlencoded::Serializer::new(String::new());
25 encoded = encoded
26 .append_pair("grant_type", "refresh_token")
27 .append_pair("client_id", client_id)
28 .append_pair("refresh_token", refresh_token.secret());
29 if let Some(client_secret) = client_secret {
31 encoded = encoded.append_pair("client_secret", client_secret);
32 };
33 encoded.finish()
34 };
35
36 let url = Url::parse(&format!(
37 "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
38 ))?;
39
40 let mut req = Request::new(url, Method::Post);
41 req.insert_header(
42 headers::CONTENT_TYPE,
43 content_type::APPLICATION_X_WWW_FORM_URLENCODED,
44 );
45 req.set_body(encoded);
46
47 let rsp = http_client.execute_request(&req).await?;
48 let rsp_status = rsp.status();
49
50 if rsp_status.is_success() {
51 rsp.json().await.map_kind(ErrorKind::Credential)
52 } else {
53 let (rsp_status, rsp_headers, rsp_body) = rsp.deconstruct();
54 let rsp_body = rsp_body.collect().await?;
55 let token_error: RefreshTokenError = from_json(&rsp_body).map_err(|_| {
56 ErrorKind::http_response_from_parts(rsp_status, &rsp_headers, &rsp_body)
57 })?;
58 Err(Error::new(ErrorKind::Credential, token_error))
59 }
60}
61
62#[derive(Debug, Clone, Deserialize)]
64pub struct RefreshTokenResponse {
65 token_type: String,
66 #[serde(rename = "scope", deserialize_with = "deserialize::split")]
67 scopes: Vec<String>,
68 expires_in: u64,
69 ext_expires_in: u64,
70 access_token: Secret,
71 refresh_token: Secret,
72}
73
74impl RefreshTokenResponse {
75 pub fn token_type(&self) -> &str {
77 &self.token_type
78 }
79 pub fn scopes(&self) -> &[String] {
81 &self.scopes
82 }
83 pub fn expires_in(&self) -> u64 {
85 self.expires_in
86 }
87 pub fn access_token(&self) -> &Secret {
89 &self.access_token
90 }
91 pub fn refresh_token(&self) -> &Secret {
93 &self.refresh_token
94 }
95 pub fn ext_expires_in(&self) -> u64 {
97 self.ext_expires_in
98 }
99}
100
101mod deserialize {
102 use serde::Deserializer;
103 pub fn split<'de, D>(scope: D) -> Result<Vec<String>, D::Error>
104 where
105 D: Deserializer<'de>,
106 {
107 let string: String = serde::Deserialize::deserialize(scope)?;
108 Ok(string.split(' ').map(ToOwned::to_owned).collect())
109 }
110}
111
112#[derive(Debug, Clone, Deserialize)]
114#[allow(unused)]
115pub struct RefreshTokenError {
116 error: String,
117 error_description: String,
118 error_codes: Vec<i64>,
119 timestamp: Option<String>,
120 trace_id: Option<String>,
121 correlation_id: Option<String>,
122 suberror: Option<String>,
123 claims: Option<String>,
124}
125
126impl std::error::Error for RefreshTokenError {}
127
128impl fmt::Display for RefreshTokenError {
129 fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
130 writeln!(f, "error: {}", self.error)?;
131 if let Some(suberror) = &self.suberror {
132 writeln!(f, "suberror: {suberror}")?;
133 }
134 writeln!(f, "description: {}", self.error_description)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 fn require_send<T: Send>(_t: T) {}
143
144 #[test]
145 fn ensure_that_exchange_is_send() {
146 require_send(exchange(
147 azure_core::new_http_client(),
148 "UNUSED",
149 "UNUSED",
150 None,
151 &Secret::new("UNUSED"),
152 ));
153 }
154}