auth0_integration/
auth0.rs1use crate::{config::Auth0Config, error::AppError};
2use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, TokenData, Validation};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Serialize)]
9struct ClientCredentialsRequest<'a> {
10 grant_type: &'a str,
11 client_id: &'a str,
12 client_secret: &'a str,
13 audience: &'a str,
14}
15
16#[derive(Debug, Deserialize)]
17pub struct TokenResponse {
18 pub access_token: String,
19 pub token_type: String,
20 pub expires_in: u64,
21}
22
23#[derive(Debug, Deserialize, Serialize)]
26pub struct Claims {
27 pub sub: String,
28 pub iss: String,
29 pub aud: serde_json::Value,
30 pub exp: u64,
31 pub iat: u64,
32 #[serde(flatten)]
33 pub extra: HashMap<String, serde_json::Value>,
34}
35
36#[derive(Debug, Deserialize)]
39struct Jwks {
40 keys: Vec<Jwk>,
41}
42
43#[derive(Debug, Deserialize)]
44struct Jwk {
45 kid: String,
46 n: String,
47 e: String,
48}
49
50pub struct Auth0Client {
53 http: reqwest::Client,
54 config: Auth0Config,
55}
56
57impl Auth0Client {
58 pub fn new(config: Auth0Config) -> Self {
59 Self {
60 http: reqwest::Client::new(),
61 config,
62 }
63 }
64
65 pub async fn get_access_token(&self) -> Result<TokenResponse, AppError> {
67 let body = ClientCredentialsRequest {
68 grant_type: "client_credentials",
69 client_id: &self.config.auth0_client_id,
70 client_secret: &self.config.auth0_client_secret,
71 audience: &self.config.auth0_audience,
72 };
73
74 let res = self
75 .http
76 .post(&self.config.auth0_token_url())
77 .json(&body)
78 .send()
79 .await?;
80
81 if !res.status().is_success() {
82 let text = res.text().await.unwrap_or_default();
83 return Err(AppError::Auth0(text));
84 }
85
86 Ok(res.json::<TokenResponse>().await?)
87 }
88
89 pub async fn validate_token(&self, token: &str) -> Result<TokenData<Claims>, AppError> {
91 let header = decode_header(token)?;
92 let kid = header.kid.ok_or_else(|| AppError::InvalidToken("Missing kid".to_string()))?;
93
94 let decoding_key = self.fetch_decoding_key(&kid).await?;
95
96 let mut validation = Validation::new(Algorithm::RS256);
97 validation.set_issuer(&[self.config.auth0_issuer()]);
98 validation.set_audience(&[&self.config.auth0_audience]);
99
100 decode::<Claims>(token, &decoding_key, &validation).map_err(AppError::Jwt)
101 }
102
103 async fn fetch_decoding_key(&self, kid: &str) -> Result<DecodingKey, AppError> {
104 let jwks: Jwks = self
105 .http
106 .get(&self.config.auth0_jwks_uri())
107 .send()
108 .await?
109 .json()
110 .await?;
111
112 let key = jwks
113 .keys
114 .into_iter()
115 .find(|k| k.kid == kid)
116 .ok_or_else(|| AppError::InvalidToken(format!("No JWK found for kid: {kid}")))?;
117
118 DecodingKey::from_rsa_components(&key.n, &key.e).map_err(AppError::Jwt)
119 }
120}