0

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.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
Jonas Petraška
  • 130
  • 1
  • 9
  • 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 May 26 '18 at 16:56
  • @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 May 26 '18 at 17:09
  • No, not really. When this happened to me, I could even access through Internet Explorer, but the console application didn't work. – Camilo Terevinto May 26 '18 at 17:10
  • 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 May 26 '18 at 17:15
  • @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 May 26 '18 at 17:25
  • I've solved the problem by using `client.Policy.RequireHttps = false;` (I have used it before). However, eventhought I get the discovery client to work and even get an `access token`, I'm unable to get `http://taxrefund-identity.westeurope.azurecontainer.io/api/Users` API endpoint to give me results - it throws the same `Internal server error` I got before. With that said, I assume the problem is in the authentication flow itself - when it tries to authenticate my request to the API it throws the error (I assume it tries to get the public key from discovery endpoint to validate the token) – Jonas Petraška May 26 '18 at 18:01
  • Well, did you change that in the configuration of `AddIdentityServerAuthentication(o => ...`? – Camilo Terevinto May 26 '18 at 18:05
  • `RequireHttpsMetadata` is set to `false` already in `AddIdentityServerAuthentiation` (I've provided my configuration above), the `client.Policy.RequireHttps` is the configuration on `DiscoveryClient` - It's set while sending the request (in my case in the console application). – Jonas Petraška May 26 '18 at 18:24
  • The problem is that when you use `AddIdentityServerAuthentication`, that internally uses an `HttpClient`. That client instance is what cannot reach the endpoint (I think it's called `BackChannel`) – Camilo Terevinto May 26 '18 at 18:27
  • Well that makes sense. Maybe if I set the identityserver URL (in `AddIdentityServerAuthentication`) to localhost it would resolve this problem (since identityserver and API is in the same project/container). Or maybe you have any other ideas ? – Jonas Petraška May 26 '18 at 18:35
  • That's an option I didn't consider, you could try that yes, but not sure at all if it would work – Camilo Terevinto May 26 '18 at 18:40
  • Eureka, this fixed the problem. At last, a brief relief. Now I'm getting 401 unauthorized with my `accesstoken`, eventhought I have the right scopes set up. But that's a problem for another day. @CamiloTerevinto thanks for the insights, couldn't have done it without you :) – Jonas Petraška May 26 '18 at 19:23
  • Glad to hear that! You should post an answer with the final version so that it may help future readers with similar problems – Camilo Terevinto May 26 '18 at 19:46

1 Answers1

0

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.

Jonas Petraška
  • 130
  • 1
  • 9