supabase_auth_redux/
refresh_token.rs

1use serde::{Deserialize, Serialize};
2use tracing::{debug, error, info, instrument, trace_span, Instrument};
3
4use crate::error::AuthError;
5use crate::models::token::TokenResponse;
6use crate::util::handle_response_code;
7use crate::AuthClient;
8
9#[derive(Debug, Serialize, Deserialize)]
10struct TokenRefreshGrant {
11    pub refresh_token: String,
12}
13
14impl AuthClient {
15    /// Refreshes an authentication token to obtain new access and refresh tokens
16    ///
17    /// This method exchanges a valid refresh token for a new set of tokens, extending
18    /// the user's session without requiring them to sign in again. The new access token
19    /// can be used for API authentication.
20    ///
21    /// # Arguments
22    ///
23    /// * `token` - A valid refresh token obtained from signin or a previous refresh
24    ///
25    /// # Returns
26    ///
27    /// Returns a `TokenResponse` containing new access and refresh tokens.
28    ///
29    /// # Errors
30    ///
31    /// Returns `AuthError::InvalidParameters` if the token is empty.
32    /// Returns `AuthError::NotAuthorized` if the refresh token is invalid or expired.
33    /// Returns `AuthError::Http` if the API request fails.
34    ///
35    /// # Example
36    ///
37    /// ```rust,no_run
38    /// # use supabase_auth_redux::AuthClient;
39    /// # async fn example() -> Result<(), supabase_auth_redux::AuthError> {
40    /// let client = AuthClient::new("https://your-project.supabase.co", "your-anon-key")?;
41    ///
42    /// // After user signs in, you have their refresh token
43    /// let refresh_token = "user-refresh-token";
44    /// let new_tokens = client.refresh_token(refresh_token).await?;
45    ///
46    /// println!("New access token: {}", new_tokens.access_token);
47    /// println!("New refresh token: {}", new_tokens.refresh_token);
48    /// # Ok(())
49    /// # }
50    /// ```
51    #[instrument(skip(self))]
52    pub async fn refresh_token(&self, token: &str) -> Result<TokenResponse, AuthError> {
53        if token.is_empty() {
54            error!("empty token");
55            return Err(AuthError::InvalidParameters);
56        }
57
58        let token_grant = TokenRefreshGrant {
59            refresh_token: token.to_string(),
60        };
61
62        let resp = match self
63            .http_client
64            .post(format!(
65                "{}/auth/v1/{}",
66                self.supabase_api_url, "token?grant_type=refresh_token"
67            ))
68            .bearer_auth(&self.supabase_anon_key)
69            .header("apiKey", &self.supabase_anon_key)
70            .json(&token_grant)
71            .send()
72            .instrument(trace_span!("gotrue refresh token"))
73            .await
74        {
75            Ok(resp) => resp,
76            Err(e) => {
77                error!("{}", e);
78                return Err(AuthError::Http);
79            }
80        };
81
82        let resp_code_result = handle_response_code(resp.status()).await;
83        let resp_text = match resp.text().await {
84            Ok(resp_text) => resp_text,
85            Err(e) => {
86                log::error!("{}", e);
87                return Err(AuthError::Http);
88            }
89        };
90        debug!("resp_text: {}", resp_text);
91        resp_code_result?;
92
93        let token_response = match serde_json::from_str::<TokenResponse>(&resp_text) {
94            Ok(token_response) => token_response,
95            Err(e) => {
96                error!("{}", e);
97                return Err(AuthError::Internal);
98            }
99        };
100        info!(
101            tokens_are_nonempty =
102                !token_response.access_token.is_empty() && !token_response.refresh_token.is_empty()
103        );
104        debug!(
105            token = token_response.access_token,
106            refresh_token = token_response.refresh_token
107        );
108
109        Ok(token_response)
110    }
111}