0

I'm trying to create a simple user authentication function but I just can't get it to work. Here is the code I'm working on:

public class LoginController : ApiController
{
    private void SetPrincipal(IPrincipal principal)
    {
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = principal;
        }
    }

    public bool Login(string token)
    {                       
        //Check token
        if (.....) 
        {
            //Authenticate user
            var identity = new GenericIdentity("Test user");
            SetPrincipal(new GenericPrincipal(identity, new string[]{"Test role"}));
        }
    }

    [Authorize]
    public string TestFun()
    {
        return "Hello " + User.Identity.Name;       
    }
}

So, if I try to call method TestFun() first, it returns error code 401 like it should. However when I call method Login() it should somehow save user credentials, but this is where I get lost, I just can't get it to work.

TestFun() always returns error code 401 even if I call Login() first. If I try to put return "Hello " + User.Identity.Name; in the Login() function it returns correct username, but in the TestFun() the user is not available.

I've even tried using Sessions and FormsAuthentication but I just can't get it to work, even on this really simple example.

Can someone please tell me what am I missing?

Thanks!

2 Answers2

0

The Login method sets the principal for current request only. Just after the request completes, the principal context is wiped out so that the server can handle other requests for other users. When a new request comes, eons later from the server perspective, the principal context no longer exists and if nothing restores it, the request is unauthenticated.

To fix this you have to return something from your login method to the client. Not only bool but rather - an authentication token. Something the client could use to authenticate further requests.

It could be anything. Forms cookie would be fine as long as the client remembers to append it to further requests. Another common practice is to have a custom authentication token returned to the client and then appended by the client in a custom authentication header. And as forms cookies are handled by the Forms Authentication module, custom headers would need a custom mvc authentication filter or custom asp.net authentication module so that the token is readed, the identity is extracted and restored just before the request is about to execute.

If you don't like to bake your own token infrastructure, I would also recommend OAuth2 tokens. There is a great book that contains easy to follow examples on this and other possible authentication methods:

http://www.amazon.com/Pro-ASP-NET-Web-API-Security/dp/1430257822/ref=sr_1_1?ie=UTF8&sr=8-1&keywords=web+api+security

Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • Isn't there any easier way to do this? The token I'm sending to the Login method is actually Google OAuth2 token. The method then retrieves google id by sending a request to google and then it retrieves my private userId from my database. I'm trying to save valuable cpu time by not doing this for all the future methods. Ideally I just want to save the retrieved userId in some Session variable, but that didn't work either, the Session object was always null for some reason. – user2642075 Nov 03 '13 at 22:10
  • The token you send to google to authenticate there and the token to send to your method to authenticate there should not be confused. Also, do not be afraid of "saving cpu time", token authentication ALWAYS decrypts tokens upon every request, forms authentication does so and any other method does so. If you store that in session, you waste cpu memory. Scalability issues will hit you anyway then, and this is easier to just validate tokens upon every request. – Wiktor Zychla Nov 03 '13 at 23:42
  • If I understood you correctly there are 2 ways I could go: 1) I can send google token to all methods. At the beginning they would authenticate user with google, and then retrieve my own userId from my database. This would be easier and probably more secure because google would do all the work. It would also be slower because of the extra trip to google servers. 2) I send google token to `Login()` method which authenticates user and creates my own token and store it in my database along userId. All other methods use my own token, it's faster but I'd have to write all the security logic myself. – user2642075 Nov 04 '13 at 11:17
  • Both 1) and 2) are correct. I would not recommend 1) because of the penalty hit, using google tokens internally to authenticate users coming to your service means that you have to validate such tokens each time. Regarding 2), a common practice is to encrypt the username in your custom tokens so that when a request comes with your token, you just decrypt it. If it decrypts correctly and there is a username in it, it means that you (your server) must have encrypted it (assuming your private key hasn't lacked). – Wiktor Zychla Nov 04 '13 at 12:55
0

I just got the same issue, yes, I agreed we need to save that principal into somewhere (cookie, session) for other action to use, so, in SetPrincipal function I added

HttpContext.Current.Session["user"] = HttpContext.Current.User;

Now, the issue is how to get it back for other action, the idea popups in my mind is to extend AuthorizeAttribute and override IsAuthrized function, it will read the session first and if it found the session, it will return true, otherwise it will return false.

namespace BinZ
{
    public class MyAuthorizeAttribute:AuthorizeAttribute
    {        

        protected override bool IsAuthorized(HttpActionContext actionContext) {            
            HttpContext.Current.User = HttpContext.Current.Session["user"] as IPrincipal;
            return HttpContext.Current.User != null;
        }
    }
}

Please remember to replace [Authorize] to [MyAuthorizeAttribute] in WebApi controller.

It works for me very well.

Cheers

Box Very
  • 418
  • 4
  • 14