I'm using the healthCheck to verify the state of the services but I have an error when this service is in IIS.
In ConfigureServices:
services.AddHealthChecksUI(s =>
{
s.AddHealthCheckEndpoint("Validations", "/CheckPoints");
}).AddInMemoryStorage();
services.AddHealthChecks();
In Configure
builder.MapHealthChecksUI(setupOptions: setup =>
{
setup.UIPath = "/CheckPointsUI";
});
builder.MapHealthChecks("/CheckPoints", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
builder.MapHealthChecks("/Status");
When I run the service in localhost all works good but the same code in IIS pool I have the next error:
An invalid request URI was provided. The request URI must either be an absolute URI or BaseAdress must be set
I don't know why it behaves differently.
Related
I need to implement Client Certificate authentication on some of the endpoints in my .NET 5 Web API. So I don't want to enable HTTPS across all endpoint as described here in the MS docs. I am using Kestrel on my local machine and not IIS express or IIS.
I have tried the following three methods with no luck on either of them:
var clientCertHeaders = context.HttpContext.Request.Headers;
This one returns the normal headers for the request but no certificate.
var clientCert = context.HttpContext.Connection.ClientCertificate;
var clientCertAsync = context.HttpContext.Connection.GetClientCertificateAsync().Result;
These two both return null.
I've tried applying the following to my services:
services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "X-SSL-CERT";
options.HeaderConverter = (headerValue) =>
{
X509Certificate2 clientCertificate = null;
if(!string.IsNullOrWhiteSpace(headerValue))
{
var bytes = Encoding.UTF8.GetBytes(headerValue);
clientCertificate = new X509Certificate2(bytes);
}
return clientCertificate;
};
});
Even with that enabled in my services I am not retrieving the client certificate.
I am using Postman to make the requests to the API requests.
You need to configure Kestrel to allow client certificates in the program.cs The default value is ClientCertificateMode.NoCertificate so in your ConfigureWebHostDefaults you need to change that to ClientCertificateMode.AllowCertificate.
Here's an edited chunk of code from the docs you sent where I did that:
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureKestrel(o =>
{
o.ConfigureHttpsDefaults(o =>
o.ClientCertificateMode =
ClientCertificateMode.AllowCertificate);
});
});
}
I'm trying out OpenIddict 3.0 for use in a SSO app. I followed the steps in the documentation, created an Authorize controller, and added a test application. When I try to connect to authorize I get this exception:
System.InvalidOperationException: The authorization request was not handled. To handle authorization requests, create a class implementing 'IOpenIddictServerHandler' and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.
Alternatively, enable the pass-through mode to handle them at a later stage.
I can't find anything in the documentation or sample apps that explains what this means. What am I missing?
Here's my code so far. In Startup.cs:
services.AddOpenIddict()
.AddCore(o =>
{
o.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
})
.AddServer(o =>
{
o.SetTokenEndpointUris("/connect/token");
o.SetAuthorizationEndpointUris("/connect/authorize");
o.AllowAuthorizationCodeFlow();
o.RegisterScopes(OpenIddictConstants.Scopes.Email);
o.AcceptAnonymousClients();
o.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
o.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
})
.AddValidation(o =>
{
o.UseLocalServer();
o.UseAspNetCore();
});
And test app description:
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "test-app",
DisplayName = "Test Application",
PostLogoutRedirectUris = { new Uri("https://oidcdebugger.com/debug") },
RedirectUris = { new Uri("https://oidcdebugger.com/debug") }
};
I'm testing with the OpenID Connect debugger.
To handle authorization requests in a MVC controller, you must tell OpenIddict's ASP.NET Core host to use the pass-through mode, exactly like what you did for the token endpoint:
services.AddOpenIddict()
.AddServer(options =>
{
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough() // Add this line.
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
});
Have 2 Web API's created using .Net Core 2.0 and hosted internally in IIS under windows authentication (anonymous disabled) on same server. Both API's run on same service account as well with appropriate permissions/rolegroups in Active Directory. However, get 401 unauthorized error when consuming one API from the other. Using HTTPClient to make API calls. Note that, it works when accessing the 2nd API endpoint directly via browser but not from another API.
Decorated with Authorize filter in controller
[Authorize(Policy = "ValidRoleGroup")]
Start up code in ConfigureServices in both api services as below.
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthorization(options =>
{
options.AddPolicy("ValidRoleGroup", policy => policy.RequireRole(Configuration["SecuritySettings:ValidRoleGroup"]));
});
services.AddMvc(configure =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
configure.Filters.Add(new AuthorizeFilter(policy));
});
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = true;
options.ForwardClientCertificate = true;
});
services.AddMvc();
services.AddScoped<HttpClient>(c => new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true,
PreAuthenticate = true,
ClientCertificateOptions = ClientCertificateOption.Automatic,
}));
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
The 401 errors went away after adding registry entries as described in below article (Method 1)
https://support.microsoft.com/en-us/help/896861/you-receive-error-401-1-when-you-browse-a-web-site-that-uses-integrate
Note that the Value data should be your actual domain URL (XXX.com) and not machine name.
I am writing a test to get a token from identity server4 using Microsoft.AspNetCore.TestHost
var hostBuilder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers())
;
})
.Configure(app =>
{
app.UseIdentityServer();
});
var server = new TestServer(hostBuilder);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost:5000");
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
Then disco.Error comes up with the following error
Error connecting to
http://localhost:5001/.well-known/openid-configuration: An error
occurred while sending the request.
What am i missing?
The discovery client is obviously doing an external call to that actual address. You want it to call the test server that happens to "live" InMemory.
Take a look at these tests here for IdentityServer4 that tests the discovery document.
To answer your question though you need to use one of the overloaded methods for the DiscoveryClient that takes in a handler that would make the correct "call" to your InMemory test server. Below is an example of how this could be done.
var server = new TestServer(hostBuilder);
var handler = server.CreateHandler();
var discoveryClient = new DiscoveryClient("http://localhost:5000", handler);
var discoveryDocument = await discoveryClient.GetAsync();
Also I highly recommend going over the IdentityServer4 integration tests if youre going to be doing some of your own tests like this.
We have an ASP.NET MVC application that is authenticating without issue against IdentityServer3, however the web API part of the application using ApiController's start to fail if the user waits before proceeding with AJAX functionality after about 3 minutes (before 3 mins everything seems fine).
The errors seen in Chrome are:
XMLHttpRequest cannot load
https://test-auth.myauthapp.com/auth/connect/authorize?client_id=ecan-farmda…gwLTk5ZjMtN2QxZjUyMjgxNGE4MDg2NjFhZTAtOTEzNi00MDE3LTkzNGQtNTc5ODAzZTE1Mzgw.
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://test.myapp.com' is therefore not allowed
access.
On IE I get the following errors:
SCRIPT7002: XMLHttpRequest: Network Error 0x4c7, The operation was
canceled by the user.
Looking at IdentityServer3's logs I'm seeing entries like so:
2015-08-10 16:42 [Warning]
(Thinktecture.IdentityServer.Core.Configuration.Hosting.CorsPolicyProvider)
CORS request made for path: /connect/authorize from origin:
http://test.myapp.com but rejected because invalid CORS path
In the IdentityServer3 web application I'm giving clients AllowedCorsOrigins:
Thinktecture.IdentityServer.Core.Models.Client client = new Thinktecture.IdentityServer.Core.Models.Client()
{
Enabled = configClient.Enabled,
ClientId = configClient.Id,
ClientName = configClient.Name,
RedirectUris = new List<string>(),
PostLogoutRedirectUris = new List<string>(),
AllowedCorsOrigins = new List<string>(),
RequireConsent = false, // Don't show consents screen to user
RefreshTokenExpiration = Thinktecture.IdentityServer.Core.Models.TokenExpiration.Sliding
};
foreach (Configuration.RegisteredUri uri in configClient.RedirectUris)
{
client.RedirectUris.Add(uri.Uri);
}
foreach (Configuration.RegisteredUri uri in configClient.PostLogoutRedirectUris)
{
client.PostLogoutRedirectUris.Add(uri.Uri);
}
// Quick hack to try and get CORS working
client.AllowedCorsOrigins.Add("http://test.myapp.com");
client.AllowedCorsOrigins.Add("http://test.myapp.com/"); // Don't think trailing / needed, but added just in case
clients.Add(client);
And when registering the service I add a InMemoryCorsPolicyService:
app.Map("/auth", idsrvApp =>
{
var factory = new IdentityServerServiceFactory();
factory.Register(new Registration<AuthContext>(resolver => AuthObjects.AuthContext));
factory.Register(new Registration<AuthUserStore>());
factory.Register(new Registration<AuthRoleStore>());
factory.Register(new Registration<AuthUserManager>());
factory.Register(new Registration<AuthRoleManager>());
// Custom user service used to inject custom registration workflow
factory.UserService = new Registration<IUserService>(resolver => AuthObjects.AuthUserService);
var scopeStore = new InMemoryScopeStore(Scopes.Get());
factory.ScopeStore = new Registration<IScopeStore>(scopeStore);
var clientStore = new InMemoryClientStore(Clients.Get());
factory.ClientStore = new Registration<IClientStore>(clientStore);
var cors = new InMemoryCorsPolicyService(Clients.Get());
factory.CorsPolicyService = new Registration<ICorsPolicyService>(cors);
...
var options = new IdentityServerOptions
{
SiteName = "Authentication",
SigningCertificate = LoadCertificate(),
Factory = factory,
AuthenticationOptions = authOptions
};
...
});
I do note that the IdentityServer3 log entries say "CORS request made for path: /connect/authorize" rather than "CORS request made for path: /auth/connect/authorize". But looking through the IdentityServer3 source code suggests this probably isn't the issue.
Perhaps the InMemoryCorsPolicyService isn't being picked up?
Any ideas of why things aren't working for the AJAX called ApiController?
Thinktecture.IdevtityServer3 v1.6.2 has been installed using NuGet.
Update
I'm having a conversation with the IdentityServer3 developer, but am still having an issue reaching a resolution. In case it helps:
https://github.com/IdentityServer/IdentityServer3/issues/1697
Did you try adding https url also?- client.AllowedCorsOrigins.Add("https://test.myapp.com");
The documentation of IdentityServer says you should configure it on the client:
AllowedCorsOrigins = ... // Defaults to the discovery, user info, token, and revocation endpoints.
https://docs.duendesoftware.com/identityserver/v6/reference/options/#cors
CORS is a nightmare!
It's a browser thing which is why you're witnessing different behaviour in IE than in Chrome.
There are (at least) two ways that CORS is configured on the server. When a client makes a request with the Origin header you have to tell the server whether or not to accept it -- if accepted then the server adds the Access-Control-Allow-Origin header to the response for the browser.
In MVC / webAPI you have to add CORS services, set a CORS policy, and then .UseCors something like this:
builder.Services.AddCors((options =>
{
if (settings.AllowedCorsOrigins.Length > 0)
{
options.AddDefaultPolicy(builder =>
{
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
builder.AllowAnyHeader().AllowAnyMethod().WithOrigins(settings.AllowedCorsOrigins);
});
}
if (isDevelopment)
{
options.AddPolicy("localhost", builder =>
{
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
builder.AllowAnyHeader().AllowAnyMethod().SetIsOriginAllowed((string origin) => { return origin.Contains("localhost"); }); });
}
});
and
app.UseCors();
if (app.Environment.IsDevelopment())
{
app.UseCors("localhost");
}
Typically, you want the list of allowed hosts as an array of strings in your appsettings.json. And watch out for the boobytrap with SetIsOriginAllowedToAllowWildcardSubdomains.
As well as this, IdentityServer has its own additional CORS settings which are applied in addition to the standard MVC/webAPI settings. These are in the ClientCorsOrigin table and this doesn't support wildcard subdomains. You can sidestep this whole boobytrap by implementing your own ICorsPolicyService to use the same settings from your appsettings.json something like this
public class CorsPolicyService : ICorsPolicyService
{
private readonly CorsOptions _options;
public CorsPolicyService(IOptions<CorsOptions> options)
{
_options = options.Value;
}
private bool CheckHost(string host)
{
foreach (string p in _options.AllowedCorsOrigins)
{
if (Regex.IsMatch(host, Regex.Escape(p).Replace("\\*", "[a-zA-Z0-9]+"))) // Hyphen?
{
return true;
}
}
return false;
}
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(CheckHost(origin));
}
}