Secure Web API with Form Authentication (Authorize) and Basic Authentication (Message Handler) together
Solution 1
There are many examples online as to how best to wrap Web API using Authorization (see Toan's answer). I do know it's possible to secure web api with Forms Authentication tokens and use the attributes in your example. I don't believe you should have to write your own Message Handler to do it either.
http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api
Start by looking at this web page. Also, Toan's answer and link are also great suggestions. You are certainly on the right track. You can also get a good feel for how security works in ASP.NET Web API by building MVC Templates with Authentication as those samples include an Account Management Controller and all the code to do authentication and authorization.
Assuming you have properly setup Forms Authentication within your website. When the Web API Method is called (Get, Post, Put, Delete, Options) the Controller.User will be a populated IPrincipal object containing a Name, IsAuthenticated bool and a List of Roles. These values are controlled by your Forms Authentication piece AND are what are queried by the framework when you use the [AllowAnonymous] or [Authorize] attributes.
Please Note: Using Forms Authentication without SSL is a really, really bad thing as credentials are shared as clear text. Forms Auth is also susceptible to Cross Site Request Forgery
Here is an example I use in MVC4 to perform Forms Authentication on Web API using a Super Class called BaseApiController
public BaseApiController()
{
CurrentUser = new ScrubbedUser(User);
}
protected ScrubbedUser CurrentUser { get; set; }
Then in my ScrubbedUser Class I retrieve the User's information from a Database (or Cache/Session) remembering that the user could be Anonymous
public class ScrubbedUser
{
private IPrincipal Principal { get; set; }
public ScrubbedUser(string principal)
{
Principal = null;
if (string.IsNullOrEmpty(principal))
{
Profile = GetDefaultProfile();
}
else
{
Profile = GetUserProfile(principal);
}
//Get User Memberships
Memberships = GetUserMemberships();
Settings = GetUserSettings();
}
public SurgeStreetUser(IPrincipal principal)
{
Principal = principal;
if (Principal == null
|| Principal.Identity == null
|| Principal.Identity.IsAuthenticated == false
|| string.IsNullOrEmpty(Principal.Identity.Name))
{
Profile = GetDefaultProfile();
}
else
{
Profile = GetUserProfile(Principal.Identity.Name);
}
//Get User Memberships
Memberships = GetUserMemberships();
Settings = GetUserSettings();
}
public UserProfile Profile { get; private set; }
public List<V_UserMembership> Memberships { get; private set; }
public List<Setting> Settings { get; private set; }
private UserProfile GetDefaultProfile()
{
//Load an Anonymous Profile into the ScrubbedUser instance any way you like
}
private UserProfile GetUserProfile(string userName)
{
//Load the UserProfile based on userName variable (origin is Principle Identity Name
}
private List<V_UserMembership> GetUserMemberships()
{
//Load User's Memberships or Roles from Database, Cache or Session
}
private UserProfile PopulateCurrentUser(UserProfile userProfile)
{
var user = new UserProfile
{
//Convenience Method to clone and set a Profile Property
};
return user;
}
private List<Setting> GetUserSettings()
{
//Get the User's Settings or whatever
}
//Convenience to return a JSON string of the User (great to use with something like Backbone.js)
private dynamic JSONRecord
{
get
{
return new
{
CustId = Profile.CustId,
UserName = Profile.UserName,
UserId = Profile.UserId,
Email = Profile.Email,
FirstName = Profile.FirstName,
Language = Profile.Language,
LastActivityDate = Profile.LastActivityDate,
LastName = Profile.LastName,
DebugOption = Profile.DebugOption,
Device = Profile.Device,
Memberships = Memberships,
Settings = Settings
};
}
}
}
I use Memberships instead of Roles and can use the CurrentUser property of the Super Class to test to see if the user is a member of something. I can also use the [Authorize] attribute on the Web API Controller at the class level or at the method level
public class ListController : BaseApiController
{
//main is "global"
public dynamic Get(string id)//JObject values)
{
//I can test here for anonymous as well, even if I allow anonymous
//Example using my own convention on User Profile Class populated by ScrubbedUser constructor
if (CurrentUser.Profile.CustId == "public")
{
return HttpStatusCode.Forbidden;
}
//Redundant Code
if (!User.Identity.IsAuthenticated)
{
return HttpStatusCode.Forbidden;
}
string filterExt = string.IsNullOrEmpty(id) || id=="global"
? "*" : id;
return ListRepository.GetList(filterExt, SSUser);
}
[Authorize]
public dynamic Post(JObject values)
{
//Just a sample, this will not fire unless the user is authenticated
return CurrentUser.JSONRecord;
}
}
Solution 2
I think there are some misunderstanding in terms of how to secure Web API. What is the purpose of using Forms-based authentication here?
You have the basic authentication which authenticates a user. Why do you need to use Forms-based authentication to authenticate the user against?
If you want to verify user's permissions, then map the user credential to a set of claims and check it on your API controller.
You can have a look at the following link http://channel9.msdn.com/Shows/Web+Camps+TV/Securing-ASPNET-Web-APIs
chemitaxis
Updated on June 07, 2022Comments
-
chemitaxis almost 2 years
I am trying to use Form Authentication (Filter Attribute) and Basic Authentication (Message Handler) together. I know that the pipeline of the Web API executes before the Message Handler than Filters.
I have tried everything and I have tried to search in google a lot but I can't find a solution, I only can do work the authentication and authorice separated but not together.
Code:
TestController.cs
public class TestController : ApiController { private readonly worldEntities _db = new worldEntities(); // GET api/Country [Authorize] public Capital Get() { var capital = new Capital { CapitalCountry = _db.cities.FirstOrDefault(), Country = _db.countries.FirstOrDefault() }; capital.Cities = _db.cities.Where(s => s.CountryCode == capital.Country.Code); _db.SaveChanges(); return capital; } // Post api/Country public Capital Post() { var capital = new Capital { CapitalCountry = _db.cities.FirstOrDefault(), Country = _db.countries.FirstOrDefault() }; capital.Cities = _db.cities.Where(s => s.CountryCode == capital.Country.Code); _db.SaveChanges(); return capital; } }
WebApiConfig.cs
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services config.MessageHandlers.Add(new BasicAuthMessageHandler()); config.Filters.Add(new AuthorizeAttribute()); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } }
BasiAuthMessagHandle.cs
public class BasicAuthMessageHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var headers = request.Headers; if (headers.Authorization != null && headers.Authorization.Scheme == "Basic") { var userPwd = Encoding.UTF8.GetString(Convert.FromBase64String(headers.Authorization.Parameter)); var user = userPwd.Substring(0, userPwd.IndexOf(':')); var password = userPwd.Substring(userPwd.IndexOf(':') + 1); // we suppose that it's ok var principal = new GenericPrincipal(new GenericIdentity(user), null); PutPrincipal(principal); } return base.SendAsync(request, cancellationToken); } private void PutPrincipal(IPrincipal principal) { Thread.CurrentPrincipal = principal; if (HttpContext.Current != null) { HttpContext.Current.User = principal; } } }
AuthController.cs
public class AuthController : ApiController { public string Get(string id) { FormsAuthentication.SetAuthCookie(id ?? "FooUser", false); return "You are autenthicated now"; } }
Web.Config
<authentication mode="Forms" />
Thank you so much!!