17

I've been implementing IdentityServer4 to provide authorization for my React application. I have this working in my local dev environment, but am running into issues after deployed to IIS in Windows Server 2016. I am able to generate an access token via the /connect/token endpoint, but when I attempt to access a protected API using the token I get the following exception:

System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'System.String'.
 ---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'System.String'.
 ---> System.Net.Http.HttpRequestException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (dev-drydata-auth.universal-compliance.com:443)
 ---> System.Net.Sockets.SocketException (10060): A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|283_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.DefaultConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
   at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
   --- End of inner exception stack trace ---
   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
   at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)
   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
   --- End of inner exception stack trace ---
   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at NSwag.AspNetCore.Middlewares.SwaggerUiIndexMiddleware.Invoke(HttpContext context)
   at NSwag.AspNetCore.Middlewares.RedirectToIndexMiddleware.Invoke(HttpContext context)
   at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
   at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
   at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()

My ConfigureServices as follows:

public void ConfigureServices(IServiceCollection services)
{
    ConfigureDryDataServices(services);

    services.AddControllersWithViews();

    services.AddCors(options =>
    {
        options.AddPolicy("AllOrigins",
        builder =>
        {
            builder.AllowAnyMethod()
                   .AllowAnyHeader()
                   .AllowAnyOrigin();
        });
    });
    services.AddScoped<IClaimsTransformation, WebAppCalimsTransformation>();
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(o =>
    {
        o.Authority = Configuration.GetValue<string>("AppSettings:Auth:ServerUrl");
        o.Audience = Configuration.GetValue<string>("AppSettings:Auth:Audience");
        o.RequireHttpsMetadata = false;
        o.Events = new JwtBearerEvents
        {
            OnTokenValidated = context =>
            {
                if (context.SecurityToken is JwtSecurityToken accessToken && context.Principal.Identity is ClaimsIdentity identity)
                {
                    identity.AddClaim(new Claim("access_token", accessToken.RawData));
                }

                return Task.CompletedTask;
            }
        };
    });

    services.AddAuthorization(options =>
    {
        options.AddPolicy("ApiReader", policy => policy.RequireClaim("scope", "my_api_software"));
        options.AddPolicy("admin", policy => policy.RequireClaim(ClaimTypes.Role, "admin"));
        options.AddPolicy("user", policy => policy.RequireClaim(ClaimTypes.Role, "user"));
    });

    services.AddHttpClient("Auth", config =>
    {
        config.BaseAddress = new Uri(Configuration.GetValue<string>("AppSettings:Auth:ServerUrl"));
    });

    // In production, the React files will be served from this directory
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "ClientApp/build";
    });


    services.AddSwaggerDocument(config => {
        config.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT token"));
        config.AddSecurity("JWT token", new OpenApiSecurityScheme
        {
            Type = OpenApiSecuritySchemeType.ApiKey,
            Name = "Authorization",
            Description = "Copy 'Bearer ' + valid JWT token into field",
            In = OpenApiSecurityApiKeyLocation.Header
        });
        config.PostProcess = (document) =>
        {
            document.Info.Version = "v1";
            document.Info.Title = "My API API";
            document.Info.Description = "ASP.NET 5.0 My API";
        };
    });
}

This is also working fine when deploying local IIS in my pc

Jafin
  • 4,153
  • 1
  • 40
  • 52
Rayan
  • 181
  • 1
  • 1
  • 4
  • 1
    Stack trace says that your OpenId configuration discovery endpoint (`.well-known/openid-configuration`) is not accessible – AndrewSilver Dec 11 '21 at 12:17
  • @ AndrewSilver, Thank you for showing me the right direction. Seems I'm unable to access OpenId configuration discovery endpoint in the browser inside the server as well. I added the endpoint to the Trusted site list in the internet explorer but still blocking it. I think there should be some configuration inside the server to grant access by the server administrator. – Rayan Dec 12 '21 at 02:18

12 Answers12

14

The problem is that the API can't reach your IdentityServer from within your deployment, as defined in the code here:

}).AddJwtBearer(o =>
{
    o.Authority = Configuration
        .GetValue<string>("AppSettings:Auth:ServerUrl");

So, via networking/DNS make sure the Authority in the API is actually reachable from within your server. Even if they are all reachable from your browser, it does not mean the API can reach your IdentityServer from within the local network on the server side.

Jafin
  • 4,153
  • 1
  • 40
  • 52
Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • You are correct, seems I'm unable to access OpenId configuration discovery endpoint in the browser inside the server as well. I added the endpoint to the Trusted site list in the internet explorer but still blocking it. – Rayan Dec 12 '21 at 02:15
  • It can also be a router / DNS thingy that the router blocks traffic to "itself". One option is to use an internal service name for traffic between services and use the public name for the public traffic. Alternatively, you could in the hosts file hardcode the IP-address for the domain of your IS. Depends a bit on your deployment setup. But this is a common problem. – Tore Nestenius Dec 12 '21 at 11:47
  • Did you solve the problem? If so, feel free to accept one of the answers given to you as the acceptable one. – Tore Nestenius Jun 01 '22 at 07:30
7

In my case SSL was blocking local access

In terminal

dotnet dev-certs https --trust

ferdi
  • 71
  • 1
  • 3
3

In my case, microservices, the UI application auth URL and API auth URL was pointed to different ones.

Arun Prasad E S
  • 9,489
  • 8
  • 74
  • 87
1

Just posting this to save someone in the future some time. The problem is that the certificate you are using probably does not have a dns entry for the identityserver or the webapplication address.

You can fix this by creating a certificate with multiple dns addresses.

If you are a visual studio user like me you can easily create a certificate using New-SelfSignedCertificate

0

If, as @john-hardcash was previously pointing out, you need to create your own self-signed certificate for multiple domains, you might come across the following problem: Visual Studio is only using the certificate created with dev-certs tool. Even if you try to import your own multi-domain certificate by using the dev-certs tool, you may get the following error because your certificate is not marked as development certificate:

Dev-certs import failure

Then, a possible solution could be to follow this steps:

  1. Find or create a new regular development certificate by using dev-cert tools and write down its thumbprint.
  2. Clone that certificate using PowerShell with the following commands:
Set-Location -Path "cert:\CurrentUser\My"
$OldCert = (Get-ChildItem -Path <oldCertificateThumbprint>)
New-SelfSignedCertificate -CloneCert @OldCert -DnsName "localhost","host.docker.internal" -FriendlyName "ASP.NET Core HTTPS development certificate"
  1. Remove the old development certificate by using dev-certs clean option.

  2. Open certmgr.msc and trust your newly cloned certificated by moving it to your Trusted Root Certification Authorities folder.

  3. Export your new trusted certificate as a PFX file. You'll need to provide a name to the file and a password, so write it down because it'll be used after.

  4. Import your new certificate with this command where you'll need to provide the name and password you used in the step before (the -v option will show you all details and additional information for potential errors):

    dotnet dev-certs https --clean --import <myCertName>.pfx -p <myCertPwd> -v

Now, you may need to restart Visual Studio, but the next time you run your application again it will be using your new multi-domain certificate.

Jafin
  • 4,153
  • 1
  • 40
  • 52
sayago
  • 1
  • 1
0

In my case I was using the correct jwks url in the appsettings.development.json, but I forgot to adjust in secrets.json.

Árthur
  • 139
  • 1
  • 6
0

So this happens when your app can't reach the /.well-known/openid-configuration of the idenity server. Try it yourself in your browser, it should fail for you.

In my case, identityserver4 was running on http, but my other app was trying to reach it on https. So I changed the Authority to 'http' in my config, and it worked.

Danilo Popovikj
  • 127
  • 2
  • 3
0

In my situation I was hosting identity server in Azure App Services

Under the Web App -> Configuration -> General Settings -> Incoming Client certificates was set to "Required"

After changing this to "Ignore" it worked as it should.

Note: In a browser the .well-known/openid-configuration worked because I accepted the certificate. However, within postman I was getting the same 403. If you cancel the certificate request in a browser you will get 403 as well.

0

I'm connecting to the identity server that is hosted by our IT.

And the problem of mine is the endpoint they provided is not exactly the URL I should fill into appsettings.json.

The error is resolved after I change from

"OpenIDConnect": {
    "Authority": "https://login-itg.external.companyName.com/as/authorization.oauth2",
    "ClientId": "***",
    "ClientSecret": "***"
  }

To

"OpenIDConnect": {
    "Authority": "https://login-itg.external.companyName.com",
    "ClientId": "***",
    "ClientSecret": "***"
  }
Circle Hsiao
  • 1,497
  • 4
  • 22
  • 37
0

In my case, I was using docker-compose, and one of the projects in the dependencies was unloaded in Visual Studio. But I wasn't getting any compile time errors. I don't know how it was important!

Moslem Hadi
  • 846
  • 5
  • 18
  • 30
0

In my case, the client and identity server both starts up at the same time (same solution). So there is race condition, the client finishes first and tries to get the config /.well-known/openid-configuration, which is not yet available because the identity server is still in cold start.

Since the client caches the configuration request, it still won't load even if you do a refresh. You have to wait 5 minutes or more to refresh.

Some suggest to delay the config request by setting the config DelayLoadMetadata. See https://stackoverflow.com/a/37973837/1027250

You can put a thread delay on the client if you can't control the startup sequence.

Yorro
  • 11,445
  • 4
  • 37
  • 47
0

My experience is, i used Azure to create a token and the token was not correctly stored in System Environment Variables, to be specific, the old one was not replaced with a new one manually. So basically this error means a value/variable, maybe the URL, secret token, or clientId was misconfigured.

Sophie cai
  • 195
  • 4
  • 13