1

Pretty new to Authorization and Authentication so maybe I'm missing some important step... Just looking at numerous references, guides, and tutorials.

I may need to do something in my WebApiConfig?

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Or possibly in my Global.asax:

public class WebApiApplication : System.Web.HttpApplication
{
    private const string RootDocument = "/index.html";
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);
    }

    protected void Application_BeginRequest(Object sender, EventArgs e)
    {
        // Stuff to redirect to index.html unless it's an api url
    }
}

This is an ASP.Net Web API project targeting .NET Framework 4.5.2 with an Angular 2 frontend and I do nothing manually on the front end, maybe I need to do that? My Local Storage, Session Storage, and Cookies are all empty on the browser.

The SQL Server I'm accessing has a very simple user logon method which returns a role and userId, which I call in my repository here:

    public static DbUser Logon(AuthUser user)
    {
        var parameters = new List<SqlParameter>();
        {
            // Add parameters, get the DbUser (contains role and userId), and return the DbUser
        }
    }

Logon frontend is built with Angular 2 and makes an HttpPost call with username and password upon submit to the following API method, creating an identity and principal, and setting the Thread and HttpContext:

    // POST api/<controller>
    [HttpPost]
    public TokenUser Post(AuthUser user)
    {
        var dbUser = DBAccess.Repository.User.Logon(user);

        var identity = new ClaimsIdentity();
        identity.AddClaim(new Claim(ClaimTypes.Name, "CwRole"));
        identity.AddClaim(new Claim(ClaimTypes.Role, dbUser.AccessLevel));
        identity.AddClaim(new Claim(ClaimTypes.UserData, dbUser.ID.ToString()));

        var principal = new ClaimsPrincipal(identity);
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
            HttpContext.Current.User = principal;

        // Other stuff and a return statement =>
        // Note I'm not actually doing anything manually with the token on the front end
        // which may be why I'm not seeing it in the debugger's Resources tab...
    }

When I step through that code I can see clearly that Thread.CurrentPrincipal and HttpContext.Current.User are both being populated, appropriately it would seem.

But if I decorate an action with the [Authorize] attribute, I can't access it whether logged in or not.

        // Works fine
        public IEnumerable<ItemGroup> Get()
        {
            return DBAccess.Repository.Item.GetItemGroups();
        }

        // Responds with 401 (Unauthorized) no matter what
        [Authorize]
        public IEnumerable<RequestItem> Get()
        {
            return DBAccess.Repository.Item.GetRequestItems();
        }

So I created these methods, accessed them via browser url after the above logon process and stepped through, only to find that the User is never actually set (claims are all empty, etc...)

public class AuthController : ApiController
{
    public bool Get()
    {
        // Stepping through, looks like User.Identity is not even set...
        var authenticated = User.Identity.IsAuthenticated;
        return authenticated;
    }

    public bool Get(string role)
    {
        // As a matter of fact, User doesn't have any claims or anything...
        var user = User;
        return user != null && user.IsInRole(role);
    }
}

So what step am I missing to make the principal accessible after it's set? Do I need to access it with something other than WebApi's built-in "User" methods or set something on my configuration, or do something manually on the front end?

Methodician
  • 2,396
  • 5
  • 30
  • 49
  • Where does `user` in `var dbUser = DBAccess.Repository.User.Logon(user);` come from and where are you executing that snippet of code. Please provide a [mcve]. You snippets are currently disjointed. – Nkosi Jul 31 '16 at 20:10
  • The authorization provider should be added in the webapiconfig.cs.Read here http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api – Marcus Höglund Jul 31 '16 at 20:19
  • @Nkosi thanks for the feedback. I've added details. I just hate to put a lot of code in the question when I'm not sure what is relevant to the issue... – Methodician Aug 01 '16 at 17:32

2 Answers2

1

You are using Claim authentication, so on successful authentication you will receive a token. So in order to persist the token for subsequent/following Web Api requests you have do one of following ways to persist

  1. You have to set the authentication detail to a Cookie, which will be automatically appended by browser to all subsequent requests OR
  2. You can persist the token in browser's local storage, for following request - you have to append the Claims Bearer token to the request header [for authentication].

Option 1:

Setting Authentication cookie

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, userData);
         string encTicket = FormsAuthentication.Encrypt(ticket);
         HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
         Response.Cookies.Add(faCookie);

Accessing the cookie for authentication in subsequent requests.

HttpCookie authCookie = Request.Cookies[
             FormsAuthentication.FormsCookieName];
    if(authCookie != null)
    {
        //Extract the forms authentication cookie
        FormsAuthenticationTicket authTicket = 
               FormsAuthentication.Decrypt(authCookie.Value);
        // Create an Identity object
        //CustomIdentity implements System.Web.Security.IIdentity
        CustomIdentity id = GetUserIdentity(authTicket.Name);
        //CustomPrincipal implements System.Web.Security.IPrincipal
        CustomPrincipal newUser = new CustomPrincipal();
        Context.User = newUser;
    }

Option 2:

You can get the token and save it to browser Local Storage, and when ever you make a request to any API with Authorize keyword, make sure you are adding the Bearer token to the Request Header.

Something like this

var authData = localStorageService.get('authorizationData');
        if (authData) {
            config.headers.Authorization = 'Bearer ' + authData.token;
        }

Following article explains the token based authentication and has code sample for Angular JS, but just have a look http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/

Finally,

For validation on server side, you can write a custom authorize instead of the default one to validate the token and set the identity accordingly.

David Chelliah
  • 1,319
  • 1
  • 13
  • 24
  • Thanks. Working through this now but regarding the linked article: I'm not able to use Entity Framework with this legacy system and so far as I know this precludes use of "UserManager" and probably OAuth in general, or maybe I just can't translate... – Methodician Aug 08 '16 at 17:12
  • I found a StackOverflow topic on ASP.Net membership with Entity Framework. Hope this helps - http://stackoverflow.com/a/6449001/2952405 – David Chelliah Aug 08 '16 at 17:26
  • So I worked through your answer and ran into an issue where Response.Cookies.Add() is not available since this is an ApiController, not an MVC Controller... – Methodician Aug 08 '16 at 17:34
  • System.Web.HttpContext.Current.Response will be available for any http request. – David Chelliah Aug 08 '16 at 17:41
  • I'm still having a little trouble but I think you have answered this question the best can be done and I'm now able to set the cookie and authenticate on it. Thanks! – Methodician Aug 08 '16 at 19:08
  • I'm happy, that my answer helped you to find a working solution. – David Chelliah Aug 08 '16 at 19:40
0

OnAuthorization event occurs very early in the pipeline. Any type of ActionFilter runs after it. As I guess, you wrote your authentication code (first snippet) in action filter, or somewhere that executes after OnAuthorization event.

You should consider transferring that code to, for example, this event ( in global.asax.cs )

Application_PostAuthenticateRequest(Object sender, EventArgs e).

And the more elegant way is to implement your custom AuthorizeAttribute and get user information from Request and set Principals.

bot_insane
  • 2,545
  • 18
  • 40