How to handle different action with same route and different authorize attribute in asp .net core - asp.net-web-api

I'm developing some Web-API project with ASP.Net Core 2 using some custom middleware which is translating the authorization info that it gets from a 3rd party service into a new Claim Identity and adds it to http context user claim principal. I also have implemented some custom authorization policy provider which checks the added claim identity for required permissions and roles.
Now I wanna have two actions like below on same controller with same route:
[Route("api/[controller]")]
public class StatesController : Controller
{
[Authorize("PaiedUser")]
[HttpGet(Name = "GetStates")]
public IActionResult GetStatesPaied(StateHttpRequestParameter requestResourceParameter)
{ //some code here to handle the paied user request}
[Authorize("FreeUser")]
[HttpGet(Name = "GetStates")]
public IActionResult GetStatesFree(StateHttpRequestParameter requestResourceParameter)
{ //some code here to handle the free user request}
}
On the runtime I got this exception,
Microsoft.AspNetCore.Mvc.Internal.AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
SaMonitoring.API.Controllers.StatesController.GetStatesFree(SaMonitoring.API)
SaMonitoring.API.Controllers.StatesController.GetStatesPaied(SaMonitoring.API)
How can I achieve this behavior ?

I think the problem here is that the roles are not mutually exclusive as far as MVC is concerned. Someone could fall into both conditions which is what makes it ambiguous. I think you'd be better off checking the user's role inside a single function and providing results appropriately.

Related

Role-based authorization in ASP.NET Web API - how to set roles on the principal?

I am using recipe 10-3 in the newly released book ASP.NET Web Api 2 Recipes to support basic authentication in my Web API. This recipe utilizes a 3rd party library from Thinktecture. As seen from the below code, I am authentication the user against my own account service.
using Thinktecture.IdentityModel.WebApi.Authentication.Handler;
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
var authenticationConfiguration = new AuthenticationConfiguration();
var accountService = ServiceLocator.Get<AccountService>();
authenticationConfiguration.AddBasicAuthentication((userName, password) => accountService.Authenticate(userName, password));
config.MessageHandlers.Add(new AuthenticationHandler(authenticationConfiguration));
...
}
}
Now I want to make role-based authorization in my controllers using the Authorize attribute:
[Authorize(Roles="administrator")]
public IHttpActionResult Get()
{
...
}
My account service obviously knows about the users and their assigned roles, but this information is not available to the Authorize attibute (the roles are not set on the principal).
How do I accomplish this? Can the Thinktecture authentication handler be configured to set the roles on the principal? Or should I make my own custom Authorize attribute (deriving from the Authorize attribute)? And if so, should I override the OnAuthorization method to create and set the principal using my account service? Or maybe override the IsAuthorized method directly? Or maybe something else...
The AuthenticationHandler only does authentication. You'd need to set the roles in a separate step (e.g. in a delegating handler).
If you are on Web API v2 - I'd rather recommend switching to the basic auth OWIN middleware
https://github.com/thinktecture/Thinktecture.IdentityModel/tree/master/source/Thinktecture.IdentityModel.Owin.BasicAuthentication
This gives you full control over the principal that gets created.
https://github.com/thinktecture/Thinktecture.IdentityModel/blob/master/samples/OWIN/AuthenticationTansformation/KatanaAuthentication/Startup.cs
There is also a nuget.
I found out that the AddBasicAutentication method actually has an overload that takes a delegate for providing the roles. This is exactly what I was looking for. So now the call to AddBasicAuthentication looks like this, and everything works like a charm:
authenticationConfiguration.AddBasicAuthentication((userName, password) => accountService.Authenticate(userName, password), (username) => accountService.GetRoles(username));

ASP.net Web API RESTful web service + Basic authentication

I'm implementing a RESTful web service using ASP.Net Web Api. I have concluded to use Basic authentication + SSL to do the authentication part. What is the best/correct way to implement that?
My first attempt was to do it manually, parsing the Authorization header, decoding and verifying the user against my database. It works, but I wonder if I am missing something.
I've seen some solutions using user roles and principals. While I'm not sure what these actually do, I'm almost sure I will not be needing these, since in my database I define my own users and their roles.
Also what I haven't yet completely understand, is if the consumers of the service must sent the credentials with each request or they are somehow cached. Should my service do something in order for this to happen, or it's completely up to the consumer to handle this?
And a last question about clients making requests with javascript. Would there be any "cross domain request" problems if they try to use the service?
Jamie Kurtze provides a good explanation of using Basic Authentication here ASP.NET Web API REST Security Basics
From my understanding, if you want your requests to be stateless then each request will require the Authentication field to be set
Jamie Kurtze wraps the necessary code in a class derived from DelegateHandler, while Rick Strahl checks if the call is valid using a Filter. You can read more at his blog post on this topic at A WebAPI Basic Authentication Authorization Filter
Use basic authentication for the initial (sign in) request by adding a [BasicHttpAuthorize] attribute to the appropriate controllers/methods. Specify the Users and Roles with the attribute if desired. Define BasicHttpAuthorizeAttribute as a specialized AuthorizeAttribute like this:
public class BasicHttpAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (Thread.CurrentPrincipal.Identity.Name.Length == 0) { // If an identity has not already been established by other means:
AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
if (string.Compare(auth.Scheme, "Basic", StringComparison.OrdinalIgnoreCase) == 0) {
string credentials = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(auth.Parameter));
int separatorIndex = credentials.IndexOf(':');
if (separatorIndex >= 0) {
string userName = credentials.Substring(0, separatorIndex);
string password = credentials.Substring(separatorIndex + 1);
if (Membership.ValidateUser(userName, password))
Thread.CurrentPrincipal = actionContext.ControllerContext.RequestContext.Principal = new GenericPrincipal(new GenericIdentity(userName, "Basic"), System.Web.Security.Roles.Provider.GetRolesForUser(userName));
}
}
}
return base.IsAuthorized(actionContext);
}
}
Have the initial response include an API key for the user. Use the API key for subsequent calls. That way, the client's authentication remains valid even if the user changes username or password. However, when changing password, give the user an option to "disconnect clients", which you implement by deleting the API key on the server.
Have a look here for a good basic authentication implementation
http://leastprivilege.com/2013/04/22/web-api-security-basic-authentication-with-thinktecture-identitymodel-authenticationhandler/
there is more to read about it at:
https://github.com/thinktecture/Thinktecture.IdentityModel.45/wiki

ASP.NET Web API and Authorization

So I have an existing Windows Phone 7 app that uses my own authorization (player logs in with an alias and password and it verifies it against a db) going to an MVC web services.
Now I'm looking to move over to ASP.NET Web API and I'm a little confused as to how to add security for it?
I see there is the AuthorizeAttribute, but what do I need to do to allow them to be authorized?
Any guidance here would be appreciated.
AuthorizeAttribute is only checking against Thread.CurrentPrincipal to see if the user authorized to access the specified resource (in this case it is controller action) or not. It doesn't provide any type of authentication mechanism.
In your case, as you have username and password in place, you can do basic authentication. Best place to do this is inside a Message Handler. Here is an example: BasicAuthenticationHandler
I don't encourage you to use this as it is because there is no test behind this implementation but this should give you an idea. That class is an abstract class and when you set this as you base class for a message handler, you need to override the AuthenticateUser method and return IPrincipal. If the return value is null, that means user is not authenticated. If you provide an IPrincipal, that IPrincipal will be set and your AuthorizeAttribute can check against it.
You can, for instance, use GenericPrincipal class to create an IPrincipal. Assuming you are on ASP.NET host, you can register your authentication handler as below:
GlobalConfiguration.Configuration.MessageHandlers.Add(new MyAuthHandler());
To sum it up, do the authentication through a Message Handler somehow, no matter what type of authentication you use (Basic Auth, OAuth, etc.). And then, do the authorization through AuthorizeAttribute.
Also, Dominick Baier has a nice presentation on Securing ASP.NET Web
APIs and I recommend you to check that out.
maybe this will help you: Custom AuthorizeAttribute for Web API controllers

Using AuthorizeAttribute in ASP.NET Web API

I have used the [Authorize] attribute in an ASP.NET Web API method
[Authorize]
public IEnumerable<User> GetAllUsers()
From the application I can access this method without a problem as my user is already authenticated. However, is there any way that I can specify the username, password to this method when calling so that I can implement a REST API? This is when calling from a standalone application or the browser.
I didn't entirely understand your question but System.Web.Http.AuthorizeAttribute checks against Thread.CurrentPrincipal to see if the user is authorized or not.
You can specifically give user permissions if you want as below:
[Authorize(Users = "User1")]
public IEnumerable<User> GetAllUsers()
But the Authentication process is entirely up to you. I would recommend authenticating the user through a message handler and then populating the Thread.CurrentPrincipal there. Then, use the AuthorizeAttribute as you see fit for your application.

Unable to complete Azure ACS Authentication in MVC Applications

I have an MVC 3 Application that I am trying to integrate with Azure-hosted ACS Identity Providers. I have been following the Tutorials but they do not appear to be working for me when using ASP.NET MVC.
Essentially, when I hit the View which I've flagged with [Authorize] the user is redirected to the Azure-hosted Login page with the list of Identity Providers. I choose a provider (in this case Live) and log in. At this point this all works as I expect. After I successfully authenticate it appears (visually) that I'm not redirected back to my application, instead I'm returned to the Identity Providers page. When watching this in Fiddler, it appears it actually returns but then starts the cycle all over again (HTTP Status Code 302).
Can someone explain what may be causing this?
Within the Azure portal, I have the following Urls configured for my relying party application
Realm: http: //localhost:7777/
Return Url: http: //localhost:7777/ (I also tried http: //localhost:7777/Home/About)
In all other cases I have the default settings
The urls match what is in the Web.config (including the trailing slash)
There is only one controller with the following:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[Authorize]
public ActionResult About()
{
Response.Write("Claims Received from ACS:");
ClaimsIdentity ci = Thread.CurrentPrincipal.Identity as ClaimsIdentity; foreach (Claim c in ci.Claims)
{
Response.Write("Type: " + c.ClaimType + "- Value: " + c.Value + "");
}
return View();
}
}
Note: This is a brand new project created to work through this integration. All the packages and related SDKs are all up-to-date.
I would like to know whether you mean is when you log in and return to your web application, it thinks you’re not logged in, and redirects you to the identity provider’s sign in page again.
Please check if you’ve configured the authorization logic correctly. For example, if you use role based authorization, it is needed to configure ACS to create a rule that returns a role. You can also use custom authorization instead of the Authorization attribute. In your custom authorization code, you can check if the required claims are present. The claims can be role or anything else (user name, age, etc.). Custom authorization is usually more agile than the Authorization attribute.
Best Regards,
Ming Xu.
Please make sure you haven’t modified the sample code. Since it is an official ACS SDK sample, a lot of people have tried it and it will work.
Also in your original post, you mentioned you’ve configured ASP.NET authorization:
<authorization>
<deny users="?" />
</authorization>
Please remove this (as indicated in the document), if you don’t want to use ASP.NET authorization(you want to use WIF).
Best Regards,
Ming Xu.

Resources