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}