Skip to content

Allow localhost authority for mocking#3642

Open
jlaundry wants to merge 1 commit intoAzure:mainfrom
jlaundry:fix-localhost-authority
Open

Allow localhost authority for mocking#3642
jlaundry wants to merge 1 commit intoAzure:mainfrom
jlaundry:fix-localhost-authority

Conversation

@jlaundry
Copy link

Since azure_identity 0.28, only HTTPS authority endpoints have been allowed. This creates a problem when unit testing, for example, when we want to test our own SpecificAzureCredential implementation.

This PR simply allows HTTP authority endpoints when the host address is localhost.

Signed-off-by: Jed Laundry <jlaundry@jlaundry.com>
@github-actions github-actions bot added Azure.Identity The azure_identity crate Community Contribution Community members are working on the issue customer-reported Issues that are reported by GitHub users external to the Azure organization. labels Jan 30, 2026
@github-actions
Copy link

Thank you for your contribution @jlaundry! We will review the pull request and get back to you soon.

@heaths
Copy link
Member

heaths commented Jan 30, 2026

@chlowell can you take a look?

Copy link
Member

@chlowell chlowell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing http://localhost as the base URL for Entra ID is weird and scary because that's definitely not the base URL for Entra ID. I recommend instead following the SDK's approach for testing credentials against a fake Entra: implement azure_core::http::HttpClient for some type to return programmatically defined responses instead of sending HTTP requests i.e., move your mock in-proc. See for example ClientSecretCredential tests and implementation of MockSts. If you prefer the external server approach, you could use a mock transport to send your credential's outgoing requests to that server, or a pipeline policy to change those requests' URLs.

@github-project-automation github-project-automation bot moved this from Untriaged to In Progress in Azure Identity SDK Improvements Feb 2, 2026
@jlaundry
Copy link
Author

jlaundry commented Feb 3, 2026

I recommend instead following the SDK's approach for testing credentials against a fake Entra: implement azure_core::http::HttpClient for some type to return programmatically defined responses instead of sending HTTP requests i.e., move your mock in-proc

You're right, that would work for what I wrote in the description, but I pressed Enter too quickly:

Allowing http://localhost as the base URL for Entra ID is weird and scary because that's definitely not the base URL for Entra ID.

I'm also considering integration test cases where we want to run the test suite on developer laptops. In my case this is a mock Log Analytics API, but azurite --oauth basic is probably a better example, as that's definitely not something that can done in proc.

In these cases, I don't need a token from Entra ID, I just need a token that the mock service will accept.

Unless I'm missing something, the options I can see for these integration test cases are (sorted by badness asc):

  • allowing http for localhost addresses
  • add to the test suite a process to generate a local CA certificate; either add it to the local CA set, or start the process with SSL_CERT_FILE; and host the mock endpoints on https://localhost
    • I haven't tested this, but my understanding is that using SSL_CERT_FILE would require azure_identity to have the rustls-tls-native-roots feature enabled.
  • hosting our own mock token authority via a Function/Lambda
  • make every developer install Azure CLI and have their own Azure account - for a public project that isn't solely focused on Azure, it's doable but not ideal
    • there is some risk that a developer's real production credentials get logged/stolen this way - I don't really like how people use real tokens with Azurite, but that's a separate issue...
  • restricting the test suite to a CI platform - not great for developer productivity
  • distributing Entra client ID and secret/certificate credentials - bad practice in an enterprise, irresponsible for a public project
  • using public OAuth mocking services like oauth-mock.mock.beeceptor.com - really not ideal

@chlowell
Copy link
Member

chlowell commented Feb 4, 2026

I'm also considering integration test cases where we want to run the test suite on developer laptops. In my case this is a mock Log Analytics API, but azurite --oauth basic is probably a better example, as that's definitely not something that can done in proc.

In these cases, I don't need a token from Entra ID, I just need a token that the mock service will accept.

Naively--please let me know if I'm missing some detail--this seems straightforwardly achievable with the current API. If the mock service accepts a hardcoded token, you can provide that to your client via a mock credential (for example). If you want to exercise your real credential, you can give it a mock transport that returns responses containing the token (for example). If it's necessary or convenient to acquire tokens from a local server, you can use middleware in your credential's HTTP pipeline to redirect token requests to that server:

#[derive(Debug, Clone)]
struct UrlReplacingPolicy {
    url: Url,
}

impl UrlReplacingPolicy {
    fn new(url: azure_core::http::Url) -> Self {
        Self { url }
    }
}

#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl azure_core::http::policies::Policy for UrlReplacingPolicy {
    async fn send(
        &self,
        ctx: &azure_core::http::Context,
        request: &mut azure_core::http::Request,
        next: &[Arc<dyn azure_core::http::policies::Policy>],
    ) -> azure_core::http::policies::PolicyResult {
        *request.url_mut() = self.url.clone();
        next[0].send(ctx, request, &next[1..]).await
    }
}

// usage looks like this for any type that sends HTTP requests
// directly i.e., not Azure[Developer]CliCredential
let credential = azure_identity::ClientSecretCredential::new(
    "tenant ID",
    "client ID".into(),
    "secret".into(),
    Some(azure_identity::ClientSecretCredentialOptions {
        client_options: azure_core::http::options::ClientOptions {
            per_call_policies: vec![Arc::new(UrlReplacingPolicy::new(
                azure_core::http::Url::parse("http://localhost")?,
            ))],
            ..Default::default()
        },
    }),
)?;

@jlaundry
Copy link
Author

jlaundry commented Feb 5, 2026

If it's necessary or convenient to acquire tokens from a local server, you can use middleware in your credential's HTTP pipeline to redirect token requests to that server

Thank you - I had completely missed that this was possible, and I've just tried it, and your example works well (for all the cases I can think of anyway)!

I'm going to close this PR, but it may be useful for others to have this example; would it be useful if I raised a second PR with either example code and/or updated README?

@chlowell
Copy link
Member

chlowell commented Feb 6, 2026

🤔 I imagine it would be helpful to point out that applications can write their own Policy (middleware) to run arbitrary code on requests and responses. We document that in the azure_core readme (for example) because every HTTP-based client in the SDK has the same API for it, however I doubt typical devs will think to search azure_core docs for a solution to a problem they face using a client crate. Do you think you would have connected the dots if there had been a note in the azure_identity readme about customizing HTTP pipelines with a link to the azure_core docs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure.Identity The azure_identity crate Community Contribution Community members are working on the issue customer-reported Issues that are reported by GitHub users external to the Azure organization.

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants