Reputation: 251
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
Upvotes: 24
Views: 85485
Reputation: 19961
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.
One option to explore is to set the MetaDataAddress:
Try to set:
}).AddJwtBearer(o =>
{
options.MetadataAddress = "http://1.2.3.4/.well-known/openid-configuration";
...
});
Where 1.2.3.4 is the local IP or internal name of the IdentityServer instance.
In this way, you separate the what should be the address inside the tokens and from where the application gets its metadata.
For more details about this problem, visit this blog post that I wrote: IdentityServer in Docker Containers – Part 1
Upvotes: 21
Reputation: 1
For anyone who might be having a similar issue, my issue was a firewall issue.
I had a custom domain managed by Azure front door with a WAF rule that restricted access by IP.
This meant things worked fine on my machine, but when hosted, the calls were being blocked.
I added the host IP to the list of allowed IPs, and everything worked fine.
Upvotes: 0
Reputation: 12167
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 first configuration request, it still won't load even if you refresh the page. You have to wait 5 minutes or more to refresh again.
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.
Upvotes: 2
Reputation: 31
I just wanted to put this answer here for anyone that makes the same silly mistake I made. Make sure you have the library installed:
Microsoft.IdentityProtocols.OpenIdConnect
Upvotes: 3
Reputation: 26253
In my case
I added the below code to startup.cs, and it fixed my problem:
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
Upvotes: 0
Reputation: 309
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.
Upvotes: 0
Reputation: 1020
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!
Upvotes: 1
Reputation: 1977
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": "***"
}
Upvotes: 2
Reputation: 338
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.
Upvotes: 0
Reputation: 157
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.
Upvotes: 1
Reputation: 139
In my case I was using the correct jwks url in the appsettings.development.json, but I forgot to adjust in secrets.json.
Upvotes: 0
Reputation: 10135
In my case, microservices, the UI application auth URL and API auth URL was pointed to different ones.
Upvotes: 2
Reputation: 1
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:
Then, a possible solution could be to follow this steps:
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"
Remove the old development certificate by using dev-certs clean option.
Open certmgr.msc and trust your newly cloned certificated by moving it to your Trusted Root Certification Authorities folder.
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.
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.
Upvotes: 0
Reputation: 21
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
Upvotes: 1
Reputation: 101
In my case SSL was blocking local access
In terminal
dotnet dev-certs https --trust
Upvotes: 10