0
votes

I'm stuck with this problem for almost a month, so any help is appreciated. Let's get to the problem itself: I have an identity server and user management API (CRUD based) in one project. The identity server itself works as a login/register page for other web sites (I currently have only one ASP.NET framework MVC web site). The API is used to retrieve and update user profile from the MVC project and mobile app. The identity server and MVC project is backed up by docker containers.

The API authentication is done through the identity server bearer token. So, the API authentication works perfectly on localhost, however, when I deploy the identity server to Azure container instances, the API stops working, both from MVC and Postman. The error that I'm getting is :

An unhandled exception occurred while processing the request. WinHttpException: The operation timed out

System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() HttpRequestException: An error occurred while sending the request.

System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() IOException: IDX10804: Unable to retrieve document from: 'http://taxrefund-identity.westeurope.azurecontainer.io/.well-known/openid-configuration'.

Microsoft.IdentityModel.Protocols.HttpDocumentRetriever+d__8.MoveNext() InvalidOperationException: IDX10803: Unable to obtain configuration from: 'http://taxrefund-identity.westeurope.azurecontainer.io/.well-known/openid-configuration'.

Microsoft.IdentityModel.Protocols.ConfigurationManager+d__24.MoveNext()

The weirdest things is, that I can access the discovery endpoint through my browser with no problems.

My ConfigureServices method:

   public void ConfigureServices(IServiceCollection services)
    {
        services.AddEntityFrameworkSqlServer()
                .AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                        sqlServerOptionsAction: sqlOptions =>{
                            sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
                        }), ServiceLifetime.Scoped
                 );
        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
        services.AddTransient<IEmailSender, EmailSender>();
        services.AddScoped<DatabaseInitializer>();
        services.AddCors();
        // Adds IdentityServer
        var cert = new X509Certificate2(Path.Combine(Environment.ContentRootPath, "idsrv3test.pfx"), "idsrv3test");
        services.AddIdentityServer()
            .AddSigningCredential(cert)
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())
            .AddAspNetIdentity<ApplicationUser>()
            .Services.AddTransient<IProfileService, ProfileService>();

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        services.AddAuthentication()
            .AddGoogle("Google", options =>
            {
                options.ClientId = "**";
                options.ClientSecret = "**";
            })
            .AddMicrosoftAccount("Microsoft", options =>
            {
                options.ClientId = "**";
                options.ClientSecret = "**";
            });
        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(o =>
            {
                o.Authority = "http://taxrefund-identity.westeurope.azurecontainer.io/";
                o.ApiName = "Profile.API";
                o.ApiSecret = "**";
                o.RequireHttpsMetadata = false;
            });
        services.AddMvc();
        services.AddAntiforgery();
    }

Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, RoleManager<IdentityRole> roleManager, ApplicationDbContext context, UserManager<ApplicationUser> userManager)
    {
        loggerFactory.AddDebug();
        loggerFactory.AddConsole(LogLevel.Trace);
        loggerFactory.AddFile("logs.txt");

        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
        app.UseCors(policy =>
        {
            policy.AllowCredentials();
            policy.AllowAnyOrigin();
            policy.AllowAnyHeader();
            policy.AllowAnyMethod();
            policy.WithExposedHeaders("WWW-Authenticate");
        });

        app.UseStaticFiles();
        app.UseIdentityServer();
        app.UseAuthentication();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
        context.Database.Migrate();
        DatabaseInitializer.SeedData(roleManager);
    }

API resource configuration:

new ApiResource("Profile.API", "Profile management API")
{
    UserClaims = { ClaimTypes.Role },
    ApiSecrets =
    {
        new Secret("**".Sha256())
    }
}

I secure my API like this:

[Authorize(AuthenticationSchemes = "Bearer")]
[Route("api/Users")]
[Produces("application/json")]
public class ApplicationUsersAPIController : ControllerBase

To access it, I request a token from /connect/token endpoint (either with client credentials or Resource owner password/username) and then use it in the Authorization header with subsequent requests.

I have been stuck with this problem almost for a month now - it's getting frustrating now. I have read all the posts related to this problem and none of the solutions helped. I've tried degrading to earlier versions of system.net.http, different certificates and more solutions that helped others.

Also, the endpoint, without the [Authorize] attribute works just fine.

The only thing that I didn't try is to install an SSL certificate and make my urls https - I read that this shouldn't effect the functionality of identity server. I have no actual need for that right now, so let me know if it's necessary.

If there's a need for more information from me, let me know.

Much appreciated.

1
From where is the application running? Not long ago I faced the same Unable to retrieve document from error when connecting to Azure Active Directory and the problem was that Windows's proxy could not connect to the remote endpoint. I would suggest you to create a console application to try to download that URL manually. The solution should be the same for both applications then - Camilo Terevinto
@CamiloTerevinto The MVC application runs from azure container instance. I tried to download the URL with Postman and Rested - it worked like a charm, I would think that is yields the same results as a console application would, wouldn't it ? - Jonas Petraška
No, not really. When this happened to me, I could even access through Internet Explorer, but the console application didn't work. - Camilo Terevinto
Yeah, I can access the URL through the browser, as well. I will try to create a console application - I will let you know how it goes. - Jonas Petraška
@CamiloTerevinto Just as you said, the console application is unable to access the discovery endpoint, too. I get Error connecting to http://taxrefund-identity.westeurope.azurecontainer.io/.well-known/openid-configuration: HTTPS required. Just as I assumed, HTTPS is required, however, is there a workaround, because I have no need for HTTPS for now, besides, I wasn't able to get it working with docker containers and azure. - Jonas Petraška

1 Answers

0
votes

The final solution was to change the authority URL in AddIdentityServerAuthentication from:

services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(o =>
            {
                o.Authority = "http://taxrefund-identity.westeurope.azurecontainer.io/";
                o.ApiName = "Profile.API";
                o.ApiSecret = "**";
                o.RequireHttpsMetadata = false;
            });

To:

   services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(o =>
                {
                    o.Authority = "http://localhost/"; //crucial part
                    o.ApiName = "Profile.API";
                    o.ApiSecret = "**";
                    o.RequireHttpsMetadata = false;
                });

That actually makes sense, since in this case, the identity server and the API is running in the same container instance/process, so, it is not able to access it self via the DNS URL, instead, it can access itself via the localhost URL.