Skip to main content

keycloak_reqwest/
lib.rs

1use async_trait::async_trait;
2use http::Extensions;
3use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
4use reqwest::{Request, Response};
5use reqwest_middleware::{Middleware, Next, Result as MiddlewareResult};
6use std::sync::Arc;
7use std::sync::Mutex;
8
9pub struct TokenMiddleware {
10    client_id: String,
11    client_secret: String,
12    token_url: String,
13    token_cache: Arc<Mutex<Option<String>>>,
14}
15
16impl TokenMiddleware {
17    pub fn new(client_id: String, client_secret: String, token_url: String) -> Self {
18        Self {
19            client_id,
20            client_secret,
21            token_url,
22            token_cache: Arc::new(Mutex::new(None)),
23        }
24    }
25
26    async fn fetch_token(&self) -> Option<String> {
27        let client_id = &self.client_id;
28        let client_secret = &self.client_secret;
29        let token_url = &self.token_url;
30        let params = [
31            ("grant_type", "client_credentials"),
32            ("client_id", client_id.as_str()),
33            ("client_secret", client_secret.as_str()),
34        ];
35        let client = reqwest::Client::builder()
36            .danger_accept_invalid_certs(true)
37            .build()
38            .ok()?;
39        let resp = client
40            .post(token_url)
41            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
42            .form(&params)
43            .send()
44            .await
45            .ok()?;
46        if resp.status().is_success() {
47            let json: serde_json::Value = resp.json().await.ok()?;
48            json.get("access_token")
49                .and_then(|v| v.as_str())
50                .map(|s| s.to_string())
51        } else {
52            None
53        }
54    }
55}
56
57#[async_trait]
58impl Middleware for TokenMiddleware {
59    async fn handle(
60        &self,
61        mut req: Request,
62        extensions: &mut Extensions,
63        next: Next<'_>,
64    ) -> MiddlewareResult<Response> {
65        // Check if token is cached (do not hold lock across await)
66        let token_opt = {
67            let token_guard = self.token_cache.lock().unwrap();
68            token_guard.clone()
69        };
70        let token = if let Some(token) = token_opt {
71            tracing::info!("Token returning from cache");
72            token
73        } else {
74            let token = self.fetch_token().await;
75            let mut token_guard = self.token_cache.lock().unwrap();
76            *token_guard = token.clone();
77
78            tracing::info!("Fetching new token from keycloak");
79
80            token.unwrap_or_default()
81        };
82        if !token.is_empty() {
83            req.headers_mut()
84                .insert(AUTHORIZATION, format!("Bearer {}", token).parse().unwrap());
85            tracing::info!("Token set in request headers");
86        }
87        next.run(req, extensions).await
88    }
89}