azure_identity/token_credentials/
client_secret_credentials.rs1use crate::token_credentials::cache::TokenCache;
2use crate::{oauth2_http_client::Oauth2HttpClient, TokenCredentialOptions};
3use azure_core::Error;
4use azure_core::{
5 auth::{AccessToken, Secret, TokenCredential},
6 error::{ErrorKind, ResultExt},
7 HttpClient, Url,
8};
9use oauth2::{basic::BasicClient, AuthType, AuthUrl, Scope, TokenUrl};
10use std::{str, sync::Arc};
11use time::OffsetDateTime;
12
13const AZURE_TENANT_ID_ENV_KEY: &str = "AZURE_TENANT_ID";
14const AZURE_CLIENT_ID_ENV_KEY: &str = "AZURE_CLIENT_ID";
15const AZURE_CLIENT_SECRET_ENV_KEY: &str = "AZURE_CLIENT_SECRET";
16
17pub mod tenant_ids {
19 pub const TENANT_ID_COMMON: &str = "common";
23 pub const TENANT_ID_ADFS: &str = "adfs";
25}
26
27#[derive(Debug)]
32pub struct ClientSecretCredential {
33 http_client: Arc<dyn HttpClient>,
34 authority_host: Url,
35 tenant_id: String,
36 client_id: oauth2::ClientId,
37 client_secret: Option<oauth2::ClientSecret>,
38 cache: TokenCache,
39}
40
41impl ClientSecretCredential {
42 pub fn new(
44 http_client: Arc<dyn HttpClient>,
45 authority_host: Url,
46 tenant_id: String,
47 client_id: String,
48 client_secret: String,
49 ) -> ClientSecretCredential {
50 ClientSecretCredential {
51 http_client,
52 authority_host,
53 tenant_id,
54 client_id: oauth2::ClientId::new(client_id),
55 client_secret: Some(oauth2::ClientSecret::new(client_secret)),
56 cache: TokenCache::new(),
57 }
58 }
59
60 async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
61 let mut token_url = self.authority_host.clone();
62 token_url
63 .path_segments_mut()
64 .map_err(|_| {
65 Error::with_message(ErrorKind::Credential, || {
66 format!("invalid authority host {}", self.authority_host)
67 })
68 })?
69 .extend(&[&self.tenant_id, "oauth2", "v2.0", "token"]);
70
71 let mut auth_url = self.authority_host.clone();
72 auth_url
73 .path_segments_mut()
74 .map_err(|_| {
75 Error::with_message(ErrorKind::Credential, || {
76 format!("invalid authority host {}", self.authority_host)
77 })
78 })?
79 .extend(&[&self.tenant_id, "oauth2", "v2.0", "authorize"]);
80
81 let client = BasicClient::new(
82 self.client_id.clone(),
83 self.client_secret.clone(),
84 AuthUrl::from_url(auth_url),
85 Some(TokenUrl::from_url(token_url)),
86 )
87 .set_auth_type(AuthType::RequestBody);
88
89 let scopes = scopes.iter().map(ToString::to_string).map(Scope::new);
90 let oauth_http_client = Oauth2HttpClient::new(self.http_client.clone());
91 let token_result = client
92 .exchange_client_credentials()
93 .add_scopes(scopes)
94 .request_async(|request| oauth_http_client.request(request))
95 .await
96 .map(|r| {
97 use oauth2::TokenResponse as _;
98 AccessToken::new(
99 Secret::new(r.access_token().secret().to_owned()),
100 OffsetDateTime::now_utc() + r.expires_in().unwrap_or_default(),
101 )
102 })
103 .context(ErrorKind::Credential, "request token error")?;
104
105 Ok(token_result)
106 }
107
108 pub fn create(
109 options: impl Into<TokenCredentialOptions>,
110 ) -> azure_core::Result<ClientSecretCredential> {
111 let options = options.into();
112 let http_client = options.http_client();
113 let authority_host = options.authority_host()?;
114 let env = options.env();
115 let tenant_id =
116 env.var(AZURE_TENANT_ID_ENV_KEY)
117 .with_context(ErrorKind::Credential, || {
118 format!(
119 "client secret credential requires {} environment variable",
120 AZURE_TENANT_ID_ENV_KEY
121 )
122 })?;
123 let client_id =
124 env.var(AZURE_CLIENT_ID_ENV_KEY)
125 .with_context(ErrorKind::Credential, || {
126 format!(
127 "client secret credential requires {} environment variable",
128 AZURE_CLIENT_ID_ENV_KEY
129 )
130 })?;
131 let client_secret =
132 env.var(AZURE_CLIENT_SECRET_ENV_KEY)
133 .with_context(ErrorKind::Credential, || {
134 format!(
135 "client secret credential requires {} environment variable",
136 AZURE_CLIENT_SECRET_ENV_KEY
137 )
138 })?;
139
140 Ok(ClientSecretCredential::new(
141 http_client,
142 authority_host,
143 tenant_id,
144 client_id,
145 client_secret,
146 ))
147 }
148}
149
150#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
151#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
152impl TokenCredential for ClientSecretCredential {
153 async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
154 self.cache.get_token(scopes, self.get_token(scopes)).await
155 }
156 async fn clear_cache(&self) -> azure_core::Result<()> {
158 self.cache.clear().await
159 }
160}