Is it okay to use JwtBearerOptions.RequireHttpsMetadata=false in production when behind a Reverse Proxy? - asp.net-core

I have an angular application that communicates with a .Net Core REST API. I also have an authorization server that is installed on the same server as the API. The API validates the authorization token it receives from angular against the authorization server.
My plan was to set everything up behind a reverse proxy so that internal communication happens over http between the API and the authorization server in order to simplify the management of the certificates since there will be multiple instances of the services. External access would still happen over https.
In order to to do this I need to set the RequireHttpsMetadata property in JwtBearerOptions to False, but the documentation says that this should always be True when used in production. But considering that in my case the communication happens internally would it be okay if it's false?
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = "http://localhost:8080/xxxxxx";
options.RequireHttpsMetadata = false;
});
}

I was looking into the same issue and found a github issue discussing this same scenario.
The comments and code link there indicate that it validates the https connection for downloading metadata documents. So your reverse proxy should not impact this process and it should be left as true for security reasons.

Related

Add Authentication inside AutoFac ConfigureTenant

I would like to have Tenant Based Authentication on .NET Core App. I'm using AutoFac to build Tenant based Containers.
I was able to create a ServiceCollection and Populate the authentication services. However Authentication fails and getting Unauthorized response for the Tenant.
public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
multitenantContainer.ConfigureTenant("80fdb3c0-5888-4295-bf40-ebee0e3cd8f3", containerBuilder =>
{
containerBuilder.RegisterType<DataService>().As<IDataService>().InstancePerDependency();
containerBuilder.RegisterInstance(new OperationIdService()).SingleInstance();
ServiceCollection tenantServices = new();
tenantServices.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = "https://key-cloak.cloudapp.azure.com:8443/auth/realms/test";
options.Audience = "test";
});
containerBuilder.Populate(tenantServices);
});
return multitenantContainer;
}
I was able to fix it myself with the help of this article.
MultiTenant Authentication by Michael McKenna
By default handlers aren’t registered using the default “.UseAuthentication” middleware. The schemes are registered in the middleware constructor before you have a valid tenant context. Since it doesn’t support registering schemes dynamically OOTB we will need to slightly modify it.
We’re going to take the existing AuthenticationMiddleware.cs and just move the IAuthenticationSchemeProvider injection point from the constructor to the Invoke method. Since the invoke method is called after we’ve registered our tenant services it will have all the tenant specific authentication services available to it now.

Microsoft.AspNetCore.Authentication.OpenIdConnect /oauth2/authorize endpoint form - redirect_uri error

I have an application (.NET 5.0 ASP Net Core) application that I am trying to deploy to an AWS Amazon Linux 2 server. It appears that all aspects of deployment are fine except for authorization with AWS Congnito and Microsoft.AspNetCore.Authentication.OpenIdConnect. Everything works fine in dev/local and the problems only exhibit themselves when in prod deployment.
The issue exhibits itself as an "An error was encountered with the requested page." at https://auth.<mydomain>.com/error?error=redirect_mismatch&client_id=<myclientid> in the Hosted UI when trying to login. I have confirmed and reconfirmed that the Callback URL(s) are set correctly: https://sub.domain.com/signin-oidc, https://localhost:5001/signin-oidc.
My app is running on http://localhost:5000 behind an apache reverse proxy. I suspect that the non-HTTPS portion of the path between Apache and Kestrel is the issue.
What I have noticed is that Microsoft.AspNetCore.Authentication.OpenIdConnect is lacking https in the redirect_uri value that it creates as part of the /oauth2/authorize endpoint it calls.
This is what I see in Dev (no issues):
This is what I see when deployed, note that the redirect_uri is http:
In the App client settings, I can't set the signin-oidc endpoint to use the HTTP.
My ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ResponseType = "code";
options.ResponseType = Configuration["Authentication:Cognito:ResponseType"];
options.MetadataAddress = Configuration["Authentication:Cognito:MetadataAddress"];
options.ClientId = Configuration["Authentication:Cognito:ClientId"];
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "cognito:groups"
};
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = e =>
{
e.ReturnUri = string.Format("/Home/CheckProfile?url={0}", HttpUtility.UrlEncode(e.ReturnUri));
return Task.CompletedTask;
}
};
});
}
So, why is Microsoft.AspNetCore.Authentication.OpenIdConnect using HTTP when it generates the redirect_uri value of the /oauth2/authorize endpoint. Is that somethign that I need to adjust somewhere? And, does that appear to be the core issue that results in my overall https://auth.<mydomain>.com/error?error=redirect_mismatch&client_id=<myclientid> issue?
The core issue here was the reverse proxy; Kestrel running behind Apache. While I had used this setup (with certbot) regularly over the past few years, I had not previously used it with a OIDC auth scheme. The issue was the https termination at apache and the http transmission between Apache and Kestrel. An OIDC auth scheme (in my case supported by AWS Cognito) needs end-to-end https.
The "lacking https in the redirect_uri value that it creates as part of the /oauth2/authorize endpoint" was just the first of many issues I uncovered. I came up with a solution for that issue:
.AddOpenIdConnect(options =>
{
...
options.Events = new OpenIdConnectEvents
{
...
OnRedirectToIdentityProvider = async n =>
{
n.ProtocolMessage.RedirectUri = n.ProtocolMessage.RedirectUri.Replace("http://", "https://");
await Task.FromResult(0);
}
};
});
But this only solved the narrow issue of changing the redirect_uri proto; other cookie SameSite=None/Secure/http issues then appeared.
At this point, I have had success directly exposing Kestrel on 80 and 443. I realize that it's debatable whether this is a prudent idea, but it's working for me at the moment and today (Summer 2021 on .NET 5.0) it seems like Kestrel is maturing to the point where it is not one of those "only do this in development!" tools.
I found both of these articles very helpful:
https://swimburger.net/blog/dotnet/how-to-run-aspnet-core-as-a-service-on-linux
https://thecodeblogger.com/2021/05/07/certificates-and-limits-for-asp-net-core-kestrel-web-server/
Better answer. While the "Kestrel exposed to the world" answer worked, I ended up figuring out how to make the reverse proxy work with Cognito.
In the reverse proxy I ended up setting "'https' env=HTTPS" as shown here:
<VirtualHost *:*>
RequestHeader set "X-Forwarded-Proto" 'https' env=HTTPS
</VirtualHost>
I also rearanged my Prod Configure(...) as follows:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseForwardedHeaders();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseForwardedHeaders();
app.Use((ctx, next) =>
{
ctx.Request.Host = new HostString("sub.domain.com");
ctx.Request.Scheme = "https";
return next();
});
app.UseHsts();
}

How to automatically refresh token in ASP.NET Core 3.0 for API requests?

I have an ASP NET Core 3.0 application and I am using Azure Active Directory for authentication. Se below autentication configuration
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.Authority = auth.Authority;
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.SignedOutRedirectUri = auth.SignedOutRedirectUri;
options.CallbackPath = auth.CallbackPath;
options.ClientId = auth.ClientId;
})
.AddCookie(options =>
{
options.AccessDeniedPath = "/Home/AccessDenied/";
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
});
The cookie is automatically regererated when I change the page after 20 minutes of inactivity, but this isn't happen if I do a call to an API Controller. What can I do to automatically refresh token when I do an AJAX call and token is expired? Did I foorgot to add something in configuration?
This earlier post may give you some ideas - from the previous MS tech stack which followed the same concepts.
You may have some luck with the SlidingExpiration option. Traditionally though this has been a problematic area due to 2 different types of Authorize attribute.
WIDER POINT
A cleaner architecture that leads to the most standards based solution with simplest code and best UI control is to avoid mixing Web and API code in the same server side component.
Of course this is probably not in scope for your current project, but for the next one you might consider:
An ASP.Net Core Web API that validates tokens
A cookieless Web UI implemented as a single page app
Web static content hosting
The UI sends tokens to the API
The UI silently renews tokens without involving the back end
If interested have a look at my Azure AD sample:
Code
Blog Post

.net Core 3.0 & OpenId Connect - How to configure authetication to validate Azure generated non JWT access token against UserInfo in a WebAPI?

So, we have a SPA that is calling a series a microservices that sits behind an API Gateway. The authentication scheme is OpenId flow against Azure AD. So, it goes as follows: SPA (Public client) gets the access code, then the API gateway (Confidential Client) call the token service in order to get the access token; this is what it gets forwarded to the microservices themselves. So, in the end, our microservices will receive just the non-JWT Access Token. It's important to note that this token is NOT a JWT. In order to validate we're forced to use the /openId/UserInfo endpoint in the azure tenant to check the user with the access token.
We've tried used the AddOpenIdConnect extension in startup, as described [here] https://learn.microsoft.com/en-us/dotnet/architecture/microservices/secure-net-microservices-web-applications/
{
//…
// Configure the pipeline to use authentication
app.UseAuthentication();
//…
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
var callBackUrl = Configuration.GetValue<string>("CallBackUrl");
// Add Authentication services
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = identityUrl;
options.SignedOutRedirectUri = callBackUrl;
options.ClientSecret = "secret";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.RequireHttpsMetadata = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("orders");
options.Scope.Add("basket");
options.Scope.Add("marketing");
options.Scope.Add("locations");
options.Scope.Add("webshoppingagg");
options.Scope.Add("orders.signalrhub");
});
}
But this assumes the whole OpenId flow. Regardless of putting the Bearer token in the request, application redirects to the login page.
So the question is, is there any out-of-the-box configuration for this? Or we should rely in some custom handler? In such case, how the user can be properly authenticated within the context? We would need to access the HttpContext.User.Claims in the controllers themselves.
Any hint would be greatly appreciated!
Could I ask a couple of questions - partly for my own understanding:
What causes the token to not be a JWT - I don't think the link you posted explains this?
Are you using some form of token exchange from the original one issued to the SPA?
I have an equivalent solution that works, but I am using a free developer account, so maybe my setup is different:
Token validation code
Write up on messages and config
If I understand how to reproduce your setup - and a rough understanding of the config differences - I may be able to help.
Using the user info endpoint to validate tokens doesn't feel right. It would be good to get token validation working in the standard way.

Infinite auth loop when using RunProxy and OpenIdConnect

Before getting to the question - which is how do we solve the infinite authentication loop - some information regarding architecture.
We are using .net core 2.1.
We have 2 services. The first one is the one that's facing the public traffic, does the TLS termination and figures out if the request should be passed on or not. (Perhaps to other servers) When this server figures out that the request is made to a certain path, it uses RunProxy method to map the request to the 'other' service using http. That code looks like below:
app.MapWhen(<MatchRequestCondition>, proxyTime => proxyTime.RunProxy(
new ProxyOptions
{
Scheme = "http",
Host = "localhost",
Port = "1122"
}
));
As an example, if you visit https://localhost:1234/abc - this would be mapped to http://localhost:1122 - which is the port where the second application lives.
Now, this secondary service uses OpenIdConnect - the configuration of it looks like below.
// Configure Services method
services.AddMvc(mvcOptions => {
AuthorizationPolicy policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
mvcOptions.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(auth =>
{
auth.ClientId = "<client_id>";
auth.ClientSecret = "<client_secret>";
auth.Authority = "<authority>";
});
// Configure method
app.UseAuthentication();
Here's where it gets interesting:
If I visit the second node (the one that's meant to receive traffic from the first one only) directly - like http://localhost:1122 - I'm redirected to sign-in and everything works correctly.
But if I visit the first node (which is the one that the real traffic should be coming from) - it goes into a crazy authentication loop.
Any ideas to what might be the root cause? How is this different than having a load balancer in front of the regular service? Or perhaps it's because I'm using the cookie middleware in the secondary service?