Azure AD Authentication in ASP.NET Core 2.2

Im currently struggling to connect a ASP.NET Core 2.2 Web API to an existing Azure AD. I based my configuration upon this sample code by the ASP.NET Core team. Cookies were replaced with JWTs.

Unable to retrieve document from metadata adress

Now I face the following error message:

IOException: IDX10804: Unable to retrieve document from: {MetadataAdress}.

- Microsoft.IdentityModel.Protocols.HttpDocumentRetriever+<GetDocumentAsync>d__8.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever
      +<GetAsync>d__3.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.ConfigurationManager+<GetConfigurationAsync>d__24.MoveNext()

When I call the URL directly, I receive an instant response with the configuration file. However, the code does not seem to be able to do it. Im not sure what the reason could be.

Azure AD Configuration Syntax

The most likely cause of this issue is a configuration mistake. Maybe I have mistaken a field's syntax or am missing an important value.

Connection Info Fields

The connection info fields are provided like this:

    TenantId:     {Tenant-GUID}
    Authority:    https://login.microsoftonline.com/{TenantId}
    Resource:     https://{resource-endpoint}.{resource-domain}
    ClientId:     {Client-GUID}
    ClientSecret: {ClientSecret}

Service Configuration

The authentication service configuration in the Startup.cs looks like this:

    services
        .AddAuthentication(options => {  
             options.DefaultScheme          = JwtBearerDefaults.AuthenticationScheme;
             options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddJwtBearer()
        .AddOpenIdConnect(options => {
            options.ClientId             = this.ClientId;
            options.ClientSecret         = this.ClientSecret;
            options.Authority            = this.Authority;
            options.Resource             = this.Resource;
            options.ResponseType         = OpenIdConnectResponseType.CodeIdToken;
            options.SignedOutRedirectUri = "/signed-out";
            options.Events               = new OpenIdConnectEvents()
            {
                OnAuthorizationCodeReceived = async context =>
                {
                    var request = context.HttpContext.Request;
                    var currentUri = UriHelper.BuildAbsolute(
                        request.Scheme, request.Host, request.PathBase, request.Path
                    );
                    var credentials = new ClientCredential(this.ClientId, this.ClientSecret);
                    var authContext = new AuthenticationContext(
                        this.Authority,
                        AuthPropertiesTokenCache.ForCodeRedemption(context.Properties)
                    );

                    var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
                        context.ProtocolMessage.Code,
                        new System.Uri(currentUri),
                        credentials,
                        this.Resource
                    );

                    context.HandleCodeRedemption(result.AccessToken, result.IdToken);
                 }
             };

             // Custom
             options.MetadataAddress      = $"{this.Authority}/federationmetadata/2007-06/federationmetadata.xml";
             options.RequireHttpsMetadata = false; // Dev env only
        }

Existing APIs

There is a bunch of existing Web APIs that connect to this Azure AD. Sadly, they are all using the full .NET Framework. They use the UseWindowsAzureActiveDirectoryBearerAuthentication method from the Microsoft.Owin.Security.ActiveDirectory namespace's WindowsAzureActiveDirectoryBearerAuthenticationExtensions.

Another thing they use is the HostAuthenticationFilter with an authentication type of Bearer.

Questions

What is the problem?

How can I resolve this issue?

How can I use these components together?

Upvotes: 1

Views: 2894

Answers (2)

Source of the problem

After some testing I managed to identify the problem: Apparently the main cause of this issue was network related. When I switched from our company's to an unrestricted network the authentication was a success.

The fix

I had to configure a proxy and provide it to the JwtBearer and OpenIdConnect middleware. This looks like this:

var proxy = new HttpClientHandler
{
    Proxy = new WebProxy("{ProxyUrl}:{ProxyPort}") { UseDefaultCredentials = true; },
    UseDefaultCredentials = true
};

services
    .AddJwtBearer(options => {
        // ... other configuration steps ...
        options.BackchannelHttpHandler = proxy;
    })
    .AddOpenIdConnect(options => {
        // ... other configuration steps ...
        options.BackchannelHttpHandler = proxy;
    })

Metadata adress

@astaykov was right that the metadata adress is indeed incorrect. I had this feeling as well but kept it as previous APIs were running successfully with it. During problem testing I removed it, too, but it would not make a difference due to the network issues.

After the network issues were resolved, using the default metadata adress worked. The custom one failed - as expected when using a different authentication schema.

Upvotes: 1

astaykov
astaykov

Reputation: 30903

You are using OpenIDConnect libraries and point them to WS-Federation metadata (/federationmetadata/2007-06/federationmetadata.xml). This is not going to work.

The correct metadata endpoint for OpenIDConnect is /.well-known/openid-configuration. This is described here. Change that first, and then return cookies.

UPDATE

What I oversaw, was that you are protecting WebAPI. You say the middleware to use JwtBearer as default authentication cheme, but you also include a challenge scheme to be OIDC. That doesn't really make sense for me. Why do you want an OIDC challenge scheme for an WebAPI?

Here you can find the ASP.NET Core samples about JwtBearer. Here the Azure AD samples demoing WebApp calling WebApi (also bearer for the WebAPI, OIDC for the App FrontEnd.

There are no samples for JWT Bearer Auth using OIDC challenge. Why do you want to implement that? What is the case? You might be looking at implementing multiple Authentication schemes, which is possible. But not having one scheme for Authentication and another for challenge...

If by updating/removing the wrong metata changes the error message, include that in your original question. As it is now - the pure error message is that OIDC Middleware cannot parse WS-Federation metadata. Which is expected.

Upvotes: 3

Related Questions