Skip to content

Conversation

@hubert123490
Copy link
Contributor

@hubert123490 hubert123490 commented Dec 4, 2025

My solution for default azure auth model provider. Please note that Azure advises to use different authentication method for production https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet.

Simplifies authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. In production, it's better to use something else. See Usage guidance for DefaultAzureCredential.

However it should be sufficient for development purposes and further collaboration.

@geoand
Copy link
Collaborator

geoand commented Dec 4, 2025

cc @sberyozkin

@quarkus-bot

This comment has been minimized.

<artifactId>quarkus-langchain4j-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
Copy link
Contributor

@sberyozkin sberyozkin Dec 4, 2025

Choose a reason for hiding this comment

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

Thanks @hubert123490

I'm not sure this dependency belongs at the OpenAI common level... It should be at the Azure OpenAI level and the filter should be shipped there. Somehow it needs to replace the one checked at the OpenAI common level, may be by extending the QuarkusOpenAIClient, or being an Alternative CDI bean, it is a bit tricky but should be doable, thanks

@quarkus-bot

This comment has been minimized.

@hubert123490
Copy link
Contributor Author

@sberyozkin fair point
Im not quite happy abbout configMap, but it seems to work fine overall. There are some issues tho, like quite long token initialization time for the first request. Also it would be great for developer to be able to specify, which credential to use. Currently it gets first available from top to bottom (I think).
image

}

private void throwIfApiKeysNotConfigured(String apiKey, String adToken, boolean authProviderAvailable, String configName) {
if ((apiKey != null) == (adToken != null) && !authProviderAvailable) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this check have to be removed ?

import io.quarkiverse.langchain4j.auth.ModelAuthProvider;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;

public class DefaultOpenAiRestApiFilterResolver implements RestApiFilterResolver {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm just wondering, AFAIK, so far this filter can only be used with Azure OpenAI, I believe for direct OpenAI calls, OpenAI API Key is not passed as a bearer token, @geoand, is it correct ?

Perhaps we can just move this filter out of OpenAI common ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, I'm not following. With vanilla OpenAI, the authentication is a bearer token

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, so this filter has to stay

@quarkus-bot

This comment has been minimized.

import io.quarkiverse.langchain4j.openai.common.RestApiFilterResolver;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;

public class AzureRestApiFilterResolver implements RestApiFilterResolver {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe it has to become a synthetic CDI bean - you can see an example of how such beans are created in AzureOpenAI recorder - and you'd have the config properties available to the function that creates it

.resolve(builder.configName)
.ifPresent(modelAuthProvider -> restApiBuilder
.register(new OpenAiRestApi.OpenAIRestAPIFilter(modelAuthProvider)));
Map<String, String> configMap = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the original code should stay

import io.quarkus.arc.InstanceHandle;
import io.smallrye.mutiny.infrastructure.Infrastructure;

public class AzureModelAuthProviderFilter implements ResteasyReactiveClientRequestFilter {
Copy link
Contributor

Choose a reason for hiding this comment

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

It should probably extend the Open AI common one

private final ModelAuthProvider authorizer;

public AzureModelAuthProviderFilter() {
this.authorizer = new ApplicationDefaultAuthProvider();
Copy link
Contributor

Choose a reason for hiding this comment

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

We should make sure it is loaded only when no other custom model auth providers are available, for example, I linked earlier to the demo where an access token acquired after an Entra ID authorization code flow completion is propagated

MultivaluedMap<String, Object> headers) implements ModelAuthProvider.Input {
}

private static class ApplicationDefaultAuthProvider implements ModelAuthProvider {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we really need the filter resolver at all, and instead, only have this default model auth provider registered as a synthetic CDI bean in the if neither api or ad tokens or custom mode providers are available (this is the check that this PR removes but it should not)

@sberyozkin
Copy link
Contributor

Thanks @hubert123490 for working on this PR, I agree it is quite hard to make it work, IMHO this PR should probably be rewritten, please investigate how you can register the default Azure Open AI model auth provider as the synthetic CDI bean.

Effectively, the PR should only have the code for the default provider, and updates in the AzureOpenAI recorder and build step processor - the recorder is called by the processor to optionally register this default provider as a cdi bean if neither api key or id token or other model auth provider is registered.

@hubert123490
Copy link
Contributor Author

Thanks @sberyozkin for the great insight! I’ll give it a try.

@sberyozkin
Copy link
Contributor

@hubert123490 Np at all, hopefully it will be possible to do something along those lines. You can probably copy and paste one of the functions there in the AzureOpenAI recorder, alongside a matching build step and register your default application model auth provider in a similar fashion.

The only problem that may have to be solved is ensure this registration is done, before the check that this PR attempts to remove is run, i.e, the build step that registers the default model auth provider must run earlier, typically this is achieved by the step that must run earlier producing a custom build item for the next build step to expect it as one of the input parameters, @geoand can also advise. So it should be doable

@hubert123490
Copy link
Contributor Author

I've tried to add synthetic bean for model auth provider

 @BuildStep
    @Record(ExecutionTime.RUNTIME_INIT)
    public void registerDefaultModelAuthProvider(AzureOpenAiRecorder recorder,
            BuildProducer<SyntheticBeanBuildItem> producer) {
        producer.produce(
                SyntheticBeanBuildItem
                        .configure(ModelAuthProvider.class)
                        .scope(ApplicationScoped.class)
                        .setRuntimeInit()
                        .createWith(recorder.modelAuthProvider())
                        .done());
    }

I added in azure recorder

 public Function<SyntheticCreationalContext<ModelAuthProvider>, ModelAuthProvider> modelAuthProvider() {
        if (runtimeConfig.getValue().defaultConfig().apiKey().isPresent()
                || runtimeConfig.getValue().defaultConfig().adToken().isPresent()) {
            return new Function<>() {
                @Override
                public ModelAuthProvider apply(
                        SyntheticCreationalContext<ModelAuthProvider> modelAuthProviderSyntheticCreationalContext) {
                    return null;
                }
            };
        } else {
            return new Function<>() {
                @Override
                public ModelAuthProvider apply(
                        SyntheticCreationalContext<ModelAuthProvider> modelAuthProviderSyntheticCreationalContext) {
                    return new AzureOpenAiRecorder.ApplicationDefaultAuthProvider();
                }
            };
        }
    }

but that resulted in
Error
"Null contextual instance was produced by a normal scoped synthetic bean: SYNTHETIC bean [class=io.quarkiverse.langchain4j.auth.ModelAuthProvider, id=FIym7E17BGsirk4ooWp8Cr-hKM0]"
when api key or ad token is provided
and no matter what I did, I couldn't create this bean conditionally.

However I was successful with BuildConfig approach, such as I added

quarkus.langchain4j.azure-openai.azure-default-credentials-enabled=true

@ConfigRoot(phase = BUILD_TIME)
@ConfigMapping(prefix = "quarkus.langchain4j.azure-openai")
public interface LangChain4jAzureOpenAiBuildConfig {

    /**
     * Chat model related settings
     */
    ChatModelBuildConfig chatModel();

    /**
     * Embedding model related settings
     */
    EmbeddingModelBuildConfig embeddingModel();

    /**
     * Moderation model related settings
     */
    ModerationModelBuildConfig moderationModel();

    /**
     * Image model related settings
     */
    ImageModelBuildConfig imageModel();

    /**
     * If true, enables the use of Azure Default Credentials for authentication.
     * If not set, the default behavior is disabled.
     */
    @ConfigDocDefault("true")
    Optional<Boolean> azureDefaultCredentialsEnabled();
}

and in processor

    @BuildStep
    @Record(ExecutionTime.RUNTIME_INIT)
    public void registerDefaultModelAuthProvider(AzureOpenAiRecorder recorder,
            BuildProducer<SyntheticBeanBuildItem> producer,
            LangChain4jAzureOpenAiBuildConfig buildConfig) {

        if (buildConfig.azureDefaultCredentialsEnabled().isPresent() &&
                buildConfig.azureDefaultCredentialsEnabled().get()) {
            producer.produce(
                    SyntheticBeanBuildItem
                            .configure(ModelAuthProvider.class)
                            .scope(ApplicationScoped.class)
                            .setRuntimeInit()
                            .createWith(recorder.modelAuthProvider())
                            .done());
        }
    }

And that approach I've already pushed. Since it's default bean it also can be overwritten. Only downside is that even when api-key is provided with quarkus.langchain4j.azure-openai.azure-default-credentials-enabled, the azure model auth provider is used, but maybe it can be handled by warning or exception. Anyways please let me know if it is acceptable one.

@quarkus-bot
Copy link

quarkus-bot bot commented Dec 11, 2025

Status for workflow Build (on pull request)

This is the status report for running Build (on pull request) on commit 7dcda5c.

Failing Jobs

Status Name Step Failures Logs Raw logs
✔️ JVM tests - model-providers - Java 17 Logs Raw logs
✔️ JVM tests - model-providers - Java 21 Logs Raw logs
JVM tests - model-providers - Java 24 Set up JDK 24 ⚠️ Check → Logs Raw logs
Native tests - multiple-providers Setup JBang ⚠️ Check → Logs Raw logs
Native tests - rag Setup JBang ⚠️ Check → Logs Raw logs
Native tests - rag-pgvector-flyway Setup JBang ⚠️ Check → Logs Raw logs

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support default authentication via Managed Identity / Workload Identity for Azure OpenAI

3 participants