Web API, return 403 when /token call is not secure - asp.net-web-api2

We have a Web API 2 REST service. We're using the basic OWIN oAuth token authentication and we require https. We've added a RequireHttpsAttribute filter that checks and returns back a 403 HTTPS Required error when request is made with basic http.
The problem is our /token request. When we request our token we're NOT returning back the 403 error. In the Startup.Auth.cs config file we set AllowInsecureHttp=false. So this prevents users from requesting a token with an insecure call.
However, when this call is made we get a 404 Not Found error, not the 403 HTTPS Required that we want. Can anyone help me figure out how to fix this error?
I realize we're not using the RequireHttpsAttribute because this is happenign outside of the normal authentication, this is how we get the token FOR that authentication. So I'm not sure where I should be checking for the secure connection. I tried in the AuthenticationOAuthProviders class, in GrantResourceOwnersCredentials method. Before authenticating username and password I put in a check for https but I wasn't able to raise an HTTP Code error from there.

You could try using a simple Owin middle-ware at the beginning of your Owin pipeline, instead of a Web API filter. This way you'll catch every request made to your application.
Here is a small sample:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(async (ctx, next) =>
{
if (ctx.Request.Scheme.Equals(Uri.UriSchemeHttps))
await next.Invoke();
else
ctx.Response.StatusCode = 403;
});
//other middlewares
//app.UseWebApi(..)
}
}

Related

Ktor Keycloak Acess Token Route Protection

I'm using a server that authenticates through keycloak. In keycloak I have created a realm and am able to get an Access token as a response. This access token gets fed now into my Ktor application.
However, I'm not quite sure how to protect routes in an easy manner. I want to have some protected routes that have a authenticate("keycloakOAuth"){} scope around it which handles validating the access token and refreshing using the refresh token if the access token is expired.
Currently I have keycloak inside Ktor configured as this:
authenticate("keycloakOAuth") {
get("login") {}
route("/callback") {
// This handler will be executed after making a request to a provider's token URL.
handle {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
if (principal != null) {
val response = principal as OAuthAccessTokenResponse.OAuth2
call.respondText { "Access token: ${response.accessToken}" }
} else {
call.respondText { "NO principal" }
}
}
}
}
This works fine because when I go to login I'm getting sent to the Keycloak login page and I can login. When I logged in the callback executes and I get my Access Token back.
When I'm trying to protect routes however, some odd stuff happens. I know that I need to validate the incoming JWT token. But I have no clue how to given the Ktor capabilities. The examples are also of little help, since they are quite vague.
Currently I have something like this:
authenticate("keycloakOAuth") {
get("/testAuth") {
val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
if(principal != null) {
call.respondText("Authenticated!")
} else {
call.respondText("Unauthenticated...")
}
}
}
But my application will always send me to the login page and then callback page, even though I am sending the Bearer token when I'm testing this call.
My question is:
How do I protect routes in a manner that they need a valid token, with the same syntax that Ktor uses (like authenticate(){}). Do I need to configure JWT for this?
When you request one of the routes under authenticate, the full cycle of OAuth authentication is triggered. This is because the Authentication plugin is designed so a client sends credentials and gets authenticated for each request. For some reason, OAuth integration was implemented on top of the Authentication plugin hence such unexpected behavior.
To solve your problem you can have only /login and /callback routes restricted. In the callback's handler save user ID and tokens in a session or in any other storage for future use. For other routes, you can check manually the fact that a user is authenticated and then use tokens from storage to acquire protected data from the resource server. For convenience, you can create some extension functions to minimize the amount of boilerplate code. Unfortunately, there is no built-in functionality to make it work out of the box.
You don't need to configure JWT for this.

ASP.NET WebApi always returns 401 Unauthorized

I am trying to create a new endpoint in my WebApi which will be called from another Server. So, I want to set up the S2S JWT bearer based authentication for my WebApp.
The startup.cs code looks like this
public void Configuration(IAppBuilder app)
{
InitializeLoggers();
HttpConfiguration httpConfiguration = new HttpConfiguration();
WebApiConfig.Register(httpConfiguration);
app.Use<JwtS2SAuthMiddleware>();
app.UseWebApi(httpConfiguration);
}
I have added the new line here app.Use<JwtS2SAuthMiddleware>();
Inside the JwtS2SAuthMiddleware, I do the following checks
context.Request.Path.Value.StartsWith("/newendpoint");
Get the Authorization bearer token from request header
Validate the audience, issuer, issuer signing keys using the JwtSecurityTokenHandler.ValidateToken()
Verify the appId contained in JWT is in the allowed list of App Id's
Upon debugging using a request from Fiddler, I see all the above checks pass but still the response is 401 Unauthorized with a message
{"message":"Authorization has been denied for this request."}
Can someone help if I would need to anything else after doing the checks in my JwtS2SAuthMiddleware class ? Thanks.

ASP.NET Core Openiddict throws "An OpenID Connect response cannot be returned from this endpoint"

I follow instruction in openiddict server example using password flow from https://github.com/openiddict/openiddict-samples/tree/master/samples/PasswordFlow
but have no success.
It throws InvalidOperationException: An OpenID Connect response cannot be returned from this endpoint at route /connect/token:
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
Postman params:
Content-Type: application/x-www-form-urlencoded
Params: username=..&password=...&grantType=password&scope=offline_access+profile+email
I spent my day for researching but there is no information about cannot be returned from this endpoint exception. And many people can run openiddict example except me.
Here is apart of Startup.cs:
services.AddEntityFrameworkSqlite()
.AddDbContext<MisapayContext>(options =>
{
options.UseOpenIddict<int>();
});
//....
services.AddOpenIddict<int>()
.AddEntityFrameworkCoreStores<MisapayContext>()
.DisableHttpsRequirement()
.EnableTokenEndpoint("/connect/token")
.EnableLogoutEndpoint("/connect/logout")
.EnableUserinfoEndpoint("/connect/userinfo")
.UseJsonWebTokens()
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.AddEphemeralSigningKey();
services.AddMvc(config =>
{
config.Filters.Add(new ApiExceptionFilter());
}).AddJsonOptions(options =>
{
options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Local;
});
Edited: I think problem is OpenIdConnectRequest, it can not be binded if use:
OpenIddictBuiler.AddMvcBinders()
Will throws The OpenID Connect request cannot be retrieved from the ASP.NET context.`
Otherwise, remove it, OpenIdConnectRequest in AuthorizationController can get properly. And I can get request information such as username, password grantType etc... Strange... right?
Some other information:
Asp.net Core SDK 1.1
Project.json : https://gist.github.com/trinvh/47f29468887c209716098bc4c76181a7
Startup.cs: https://gist.github.com/trinvh/75b7a12fbee754d0ea8cf251f2da9fe9
AuthorizationController.cs: https://gist.github.com/trinvh/089015b2573cae550856631e72b81374
Any help will be appreciated!
Okay, here's what's happening:
You've configured OpenIddict to use /connect/token as the token endpoint address.
The token request you send via Postman points to /connect/token/, which is actually a totally different URL (/connect/token != /connect/token/).
Since the address differs from the registered endpoint path, OpenIddict doesn't handle the request and refuses to consider it as a token request.
For some reasons, MVC accepts to handle your /connect/token/ request and invokes the Exchange action, even though the route doesn't match the requested URL.
Since you haven't registered the OpenIddict MVC binder in the MVC options, MVC uses its default binder to construct the OpenIdConnectRequest object, which allows the OpenIdConnectRequest.GrantType parameter to be resolved from the invalid grantType parameter (it wouldn't happen with the dedicated OpenIddict binder).
Your token endpoint action ends up calling SignIn to return a token response.
Under the hood, OpenIddict detects that you called SignIn outside the normal token request processing - since it didn't consider the request as a token request, due to the paths difference - and aborts this unsafe operation by throwing an InvalidOperationException.
I'll ping the MVC folks to make sure they are aware of this bug.
Edit: after some research, it looks like this behavior is "by design" and was inherited from ASP.NET MVC. I opened a feature request in the aspnet/Mvc repository to add a new way to use "strict comparison" for routes matching.

Redirect unauthorized requests to Azure AD for login

I've got a WebAPI instance running in Azure that is secured with Azure AD. A mobile app connects to this API using bearer tokens, works great. When I try calling the API from the browser, though, it returns a 401 because I'm not logged in. That's true because I'm not presented with a login screen.
My API doesn't have any UI so what I'd want it to do is to forward the user to Azure AD login and return to the API endpoint they were calling after authentication.
If I go to the Azure portal, there's a setting that says "Action to take when the request is not authorized". If I set that one to "Log in with Azure Active Directory", it behaves the way I want it to. But... I have some endpoints which need to be accessed anonymously, and this setting catches all requests, not caring about any [AllowAnonymous] attributes.
So any request to an endpoint labeled Authorize that is not authorized yet should be forwarded to Azure AD login, all others should be allowed.
Add a DelegatingHandler to your web api project and register it in WebApiConfig.cs:
config.MessageHandlers.Add(new UnAuthorizedDelegatehandler());
public class UnAuthorizedDelegatehandler: DelegatingHandler
There you can check for 401 status codes and do the redirect to whatever and also apply a redirect url as querystring parameter.
HttpResponseMessage rm = await base.SendAsync(request, cancellationToken);
if (rm.StatusCode == HttpStatusCode.Unauthorized)
{
// configure the redirect here
return rm;
}

NetworkCredentials and Authorization in WebApi

I am having a few problems trying to connect to a ASP.NET webapi service (which I am running myself) from a sample console app using WebClient. The webapi is the typical sample site from MVC4:
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, new string[] { "value1", "value2" });
}
The Controller is decorated with a custom Authenticate attribute:
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null)
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.Add("WWW-Authenticate", "Basic realm=\"localhost\"");
actionContext.Response = response;
return;
}
}
The client code is the usual:
var wb = WebRequest.Create("http://localhost:64921/Values");
wb.Credentials = new NetworkCredential("xxx", "xxx");
var aaa = wb.GetResponse();
Console.WriteLine(aaa);
Console.ReadLine();
Now, I know that the WebClient or WebRequest are supposed to wait for a 401 before sending credentials and that is exactly what I am trying to do here.
Needless to say with the setup above nothing works. I have gone into the IIS express config and changed the following:
<basicAuthentication enabled="true" /> (in the security section)
<add name="BasicAuthenticationModule" lockItem="false" /> (in the modules section)
The problem that I am having is that the 401 gets returned even before the server code is actualy hit. I mean that if I stick a breakpoint into the Controller or the Attribute they are not hit. The details of the error are the usual long text about error 401.2 which I reckon is something to do with IIS configs, but using IIS express and not the nice IIS I do not have a nice GUI to fix this. Can anyone help?
Thanks a lot!
In the IIS config, you have enabled Basic auth processing, so IIS returns the 401 if there are no credentials or the credentials are invalid.
If you want your code to do the basic auth processing, then you need to tell IIS to allow anonymous access.
EDIT from comments
If you ask IIS to do basic auth it will check credentials against Windows accounts. This will act before the server code runs, so the Custom Auth Filter will not be hit. In this case the headers returned will be correct and you will see the WebClient performing the double request (one anonymous, one with credentials). If the WebClient does not use a computer or domain account (with read permissions on the folder where the site is located), the request will fail.
If you want to do authentication/authorization yourself, you need to tell IIS express not to do any auth and then do it all yourself... this basically means leaving everything as it is in the config (in your case reverting the pieces of config shown in the question) and sending the correct headers, which you already do. If you debug, you will see the Authenticate filter being hit twice, the first time it will be an anonymous that will go inside the if and generate your HTTP 401 Challenge response, the second time it will have credentials in the form of a standard Basic Authorization header: Basic <BASE64_ENCODED_CREDENTIALS>