azure_identity/
authorization_code_flow.rs

1//! Authorize using the authorization code flow
2//!
3//! You can learn more about the `OAuth2` authorization code flow [here](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow).
4
5use crate::oauth2_http_client::Oauth2HttpClient;
6use azure_core::{
7    error::{ErrorKind, ResultExt},
8    HttpClient, Url,
9};
10use oauth2::{basic::BasicClient, Scope};
11use oauth2::{ClientId, ClientSecret};
12use std::sync::Arc;
13
14/// Start an authorization code flow.
15///
16/// The values for `client_id`, `client_secret`, `tenant_id`, and `redirect_url` can all be found
17/// inside of the Azure portal.
18pub fn start(
19    client_id: ClientId,
20    client_secret: Option<ClientSecret>,
21    tenant_id: &str,
22    redirect_url: Url,
23    scopes: &[&str],
24) -> AuthorizationCodeFlow {
25    let auth_url = oauth2::AuthUrl::from_url(
26        Url::parse(&format!(
27            "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize"
28        ))
29        .expect("Invalid authorization endpoint URL"),
30    );
31    let token_url = oauth2::TokenUrl::from_url(
32        Url::parse(&format!(
33            "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
34        ))
35        .expect("Invalid token endpoint URL"),
36    );
37
38    // Set up the config for the Microsoft Graph OAuth2 process.
39    let client = BasicClient::new(client_id, client_secret, auth_url, Some(token_url))
40        // Microsoft Graph requires client_id and client_secret in URL rather than
41        // using Basic authentication.
42        .set_auth_type(oauth2::AuthType::RequestBody)
43        .set_redirect_uri(oauth2::RedirectUrl::from_url(redirect_url));
44
45    // Microsoft Graph supports Proof Key for Code Exchange (PKCE - https://oauth.net/2/pkce/).
46    // Create a PKCE code verifier and SHA-256 encode it as a code challenge.
47    let (pkce_code_challenge, pkce_code_verifier) = oauth2::PkceCodeChallenge::new_random_sha256();
48
49    let scopes = scopes.iter().map(ToString::to_string).map(Scope::new);
50
51    // Generate the authorization URL to which we'll redirect the user.
52    let (authorize_url, csrf_state) = client
53        .authorize_url(oauth2::CsrfToken::new_random)
54        .add_scopes(scopes)
55        .set_pkce_challenge(pkce_code_challenge)
56        .url();
57
58    AuthorizationCodeFlow {
59        client,
60        authorize_url,
61        csrf_state,
62        pkce_code_verifier,
63    }
64}
65
66/// An object representing an OAuth 2.0 authorization code flow.
67#[derive(Debug)]
68pub struct AuthorizationCodeFlow {
69    /// An HTTP client configured for OAuth2 authentication
70    pub client: BasicClient,
71    /// The authentication HTTP endpoint
72    pub authorize_url: Url,
73    /// The CSRF token
74    pub csrf_state: oauth2::CsrfToken,
75    /// The PKCE code verifier
76    pub pkce_code_verifier: oauth2::PkceCodeVerifier,
77}
78
79impl AuthorizationCodeFlow {
80    /// Exchange an authorization code for a token.
81    pub async fn exchange(
82        self,
83        http_client: Arc<dyn HttpClient>,
84        code: oauth2::AuthorizationCode,
85    ) -> azure_core::Result<
86        oauth2::StandardTokenResponse<oauth2::EmptyExtraTokenFields, oauth2::basic::BasicTokenType>,
87    > {
88        let oauth_http_client = Oauth2HttpClient::new(http_client.clone());
89        self.client
90            .exchange_code(code)
91            // Send the PKCE code verifier in the token request
92            .set_pkce_verifier(self.pkce_code_verifier)
93            .request_async(|request| oauth_http_client.request(request))
94            .await
95            .context(
96                ErrorKind::Credential,
97                "exchanging an authorization code for a token failed",
98            )
99    }
100}