azure_identity/
refresh_token.rs

1//! Refresh token utilities
2
3use 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
15/// Exchange a refresh token for a new access token and refresh token
16pub 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        // optionally add the client secret
30        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/// A refresh token
63#[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    /// Returns the `token_type`. Always `Bearer` for Azure AD.
76    pub fn token_type(&self) -> &str {
77        &self.token_type
78    }
79    /// The scopes that the `access_token` is valid for.
80    pub fn scopes(&self) -> &[String] {
81        &self.scopes
82    }
83    /// Number of seconds the `access_token` is valid for.
84    pub fn expires_in(&self) -> u64 {
85        self.expires_in
86    }
87    /// Issued for the scopes that were requested.
88    pub fn access_token(&self) -> &Secret {
89        &self.access_token
90    }
91    /// The new refresh token and should replace old refresh token.
92    pub fn refresh_token(&self) -> &Secret {
93        &self.refresh_token
94    }
95    /// Indicates the extended lifetime of an `access_token`.
96    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/// An error response body when there is an error requesting a token
113#[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}