I may be missing something obvious here. I'm new to both MVC and Web API, so I'm working on keeping my head above water.
I have an MVC application that interfaces with a Web API service. Authentication will be handled by a login service developed internally. When working, the MVC client should check if the current user is authenticated. If they're not, then it will redirect to this login service, which is supposed to authenticate the user and update the current user. I then need to be able to access this identity from the Web API service.
I'm operating under the assumption that the current principal (set via Thread.CurrentPrincipal or HTTPContext.Current.User) in the MVC application should be available in my Web API service, but whenever I try to access it from the service, the principal is empty. I've tried accessing the principal from the service using all of the following options, but it's always empty:
RequestContext.Principal
User.Identity
HttpContext.Current.User
Thread.CurrentPrincipal
Here's the basic idea of my code:
MVC Controller:
public ActionResult Index() {
//Just create a test principal here to see if it's available in the service
IPrincipal temp = new GenericPrincipal(new GenericIdentity("myUserName"), new string[]{});
Thread.CurrentPrincipal = temp;
using (var client = new HttpClient()) {
client.BaseAddress = new Uri("myServiceAddress");
HttpResponseMessage response = client.GetAsync("resourceString")).Result;
...Code to deal with result
}
}
Web API Controller:
[HttpGet]
public HttpResponseMessage MyAction() {
if (User.Identity == null || !User.Identity.IsAuthenticated) {
//So sad
} else {
//Do some work
}
}
The current principal is always empty, regardless of how I try to access it.
I think that you're going to need to set both the thread and context principal. Here's what I'm doing:
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) {
HttpContext.Current.User = principal;
}
}
Part way down This Article it says:
If your application performs any custom authentication logic, you must set the principal on two places:
Thread.CurrentPrincipal. This property is the standard way to set the thread's principal in .NET.
HttpContext.Current.User. This property is specific to ASP.NET.
Related
I'm developing an asp.net MVC website with the following requirements:
Develop pages for Admin and Users, these pages must be accessed
based on logged in user role: Admin or User
The website supports login only, You will call a PHP API which resides on an external website, it returns a JSON as a result that includes id, username, and role (admin, user)
You may save the result of returned json on a session to be used in your pages but this data must disappear after logout or session expiration.
I know how to develop the calling HTTP stuff and processing json, but I'm not familiar with authorization and authentication stuff, nor with using membership providers, I searched a lot and at first I thought of using SimpleMembership but I found that won't work since it depends on SQL queries and in my case I'm not going to use any type of databases.
I heard about asp.net identity but I'm not sure how to use it or if it's for my case or not, I searched again and I couldn't find any resource to help me achieve authentication and authorization for my case
I'm asking for your help to help me out and point me in the right direction
Thank you for your help
There is an example of using OAuth separated http auth API:
http://www.asp.net/web-api/overview/security/external-authentication-services
Yes, this example depends on some specified http API..
But in case when you have some another JSON/XML RPC API you can try to create your own feature like a:
public class ExternalAuthAPIClient {
public User Auth(string username, string password) { .... }
}
And use it in your AuthController in the method Login
BUT! This approach requires a lot of side changes.. where to store your user.. then create custom AuthenticateAttribure ... etc.
The better solution is to create oAuth supported API on your PHP side and use it with ASP.NET Identity.
I finally found a solution,I didn't need to use any membership providers since my website supports only login and via an API,I wrote the following code,this one is in AccountController :
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel login, string returnUrl)
{
if (!ModelState.IsValid)
{
ViewBag.Error = "Form is not valid; please review and try again.";
return View(login);
}
//Call external API,check if credentials are valid,set user role into userData
string userData="Admin";
var ticket = new FormsAuthenticationTicket(
version: 1,
name: login.Username,
issueDate: DateTime.Now,
expiration: DateTime.Now.AddSeconds(HttpContext.Session.Timeout),
isPersistent: false,
userData: userData);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Response.Cookies.Add(cookie);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", userData);
}
Then decorate admin/user controller with Authorize attribute like this:
[Authorize(Roles = "admin")]
public class AdminController : Controller
Then add the following code in Global.asax :
public override void Init()
{
base.PostAuthenticateRequest += Application_PostAuthenticateRequest;
}
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var decodedTicket = FormsAuthentication.Decrypt(cookie.Value);
var roles = decodedTicket.UserData;
var principal = new GenericPrincipal(HttpContext.Current.User.Identity, roles);
HttpContext.Current.User = principal;
}
}
While implementing a custom IAuthenticationFilter, I end up having the below code:
public class CustomAuthenticationAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
ClaimsPrincipal principal;
// logic to retrieve the claims principal
filterContext.HttpContext.User = principal;
filterContext.Principal = principal;
Thread.CurrentPrincipal = principal;
}
}
}
For a reason I can't grasp, I have 2 issues with this implementation:
The filterContext.HttpContext.User.Identity.IsAuthenticated is always false. Therefore, I always re-run my custom logic to authenticate users.
As soon as I go to a page that has a controller decorated with another Authentication action filter attribute (e.g. Authorize), the security principal is "lost" and the re-show the the "register" and "Login" links.
I strongly believe that I am not persisting the identity correctly, but I can't figure out the right way. Does anyone have a clue?
Probably you should save reference of your prinicipal to a cookie. Or you should run your logic to retrieve the claims principal for every request.
I don't familiar with claims-based identity, but anyway
filterContext.HttpContext.User = principal;
filterContext.Principal = principal;
Thread.CurrentPrincipal = principal;
is not enough to save autorization in web-application, because filter context and http context is exists for one request. I'm not sure about Thread, but for me it is not a good place to store principal in web application.
I have a MVC4 web application set up, which uses Forms authentication and Web API for interaction. All API controllers use the [Authorize] attribute.
This was working just fine out of the box, until we started adding role support. Instead of implementing a full-fledged RoleProvider, I added a list of roles to the ticket's UserData, and created the following module:
public class SecurityModule : IHttpModule
{
public void Init(HttpApplication context)
{
var roleManager = (RoleManagerModule)context.Modules["RoleManager"];
roleManager.GetRoles += GetRoles;
}
void GetRoles(object sender, RoleManagerEventArgs e)
{
var user = e.Context.User;
if (user.Identity.IsAuthenticated && !(user is MyCustomPrincipal))
{
var roles = GetRolesFromFormsAuthCookie();
if (roles != null)
e.Context.User = new MyCustomPrincipal(user.Identity, roles,
otherData);
}
e.RolesPopulated = true;
}
}
This works flawlessly for MVC calls. For API, however, even though GetRoles gets called, when it reaches the corresponding method, it's back to GenericPrincipal.
How can I make this work with Web API too? Do I have to create a DelegatingHandler?
I'm also storing some custom data in my Principal, which might be a reason not to rely on just a RoleProvider (since I'd end up with a RolePrincipal), although I could just store that in the request context.
Update: I've now added a DelegatingHandler that does the same as the IHttpModule and sets Thread.CurrentPrincipal. Is this a reasonable approach?
Have you tried to set the Thread.CurrentPrincipal in the HttpModule as well ?. You can also use a Katana handler, which will work for both, ASP.NET MVC and ASP.NET Web API.
I'm unit testing my WebAPI using HttpClient and self hosted api. I have a custom MessageHandler setting principal based on API key sent by the client. My controller is protected with [Authorize] attribute, but my test call gets in, because User.Identity is filled with my windows username. How can I make sure user isn't set when making an HttpClient call from my tests?
Your custom MessageHandler should set the User to null if the API key is not provided or invalid:
IPrincipal principal = null;
if (IsValidApiKey(someKey))
{
// The provided API key is valid => we populate the principal
principal = ...
}
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
I have an ASP.Net MVC intranet site which uses Windows Authentication to know who is logged in (no anon browsing allowed). The first time the users visit, I collect some very basic information from them for their Contact object (such as name, email, country) which is then stored in the apps database.
I want to make the site role based, so I need to be able to assign each user a role (user, admin etc). I could do this using ADS groups, but this seems rather heavyweight. Can I use the SQL Membership services provided by ASP.Net to store their usernames and then the roles they belong to, or will I be forced to collect passwords etc (defeating the point of using Windows Authentication)? Also does this integrate with the ASP.Net MVC [Authorize] attribute?
It is certainly the case in "normal" ASP.NET that you can use this combination (Windows authentication and SQL for Roles), so it should be possible for MVC too.
Here's a link that might help.
Yes, you can do this.
Authorize uses the IsInRole method of IPrincipal to determine if the user is within a given role.
You can switch out the default implementation of IPrincipal during the AuthenticateRequest event within Global.asax with your implementation that handles this your way.
Here's some sample code that might actually work and compile and not expose your website to attacks by hackers:
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
Context.User = new MyPrincipal { Identity = new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue }};
Thread.CurrentPrincipal = Context.User;
}
else
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
var identity = Db.GetIdentity(
authTicket.Name, new HttpRequestWrapper(Request));
Context.User = new MyPrincipal { Identity = new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue }};
Thread.CurrentPrincipal = Context.User;
}
}
}