22

I have a AngularJS client application that uses javascript (not coffeescript or typescript) Oauth2 to authenticate against a WebAPI 2 application using the latest Identity 2. All the software in my application is the very latest and is based on this example. My client browser targets are IE9 and above.

Note that I made some minor changes from the example above in that I do not urlencode all of the data sent to the server using the transform. Instead I urlencode only in the authenticate method below:

user.authenticate = function (userName, password, rememberMe, successCallback, errorCallback) {
    var config = {
        method: 'POST',
        url: '/Token',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        data: 'grant_type=password&username=' + encodeURIComponent(userName) + '&password=' + encodeURIComponent(password),
    };

I am developing with VS2013 Update 2 and on the server, I use C#, the latest Entity Framework and SQL Server 2012.

To login my client calls a /Token method to the WebAPI and passes the userid and password. The WebAPI then responds with a token to the client which I store. With each request to the WebAPI the token is sent back and authenticated:

$http.defaults.headers.common.Authorization = 'Bearer ' + user.data.bearerToken;

This works very well so far but as it stands the application is unable to tell the difference between users that have different roles assigned to them.

Some of the WebAPI methods can only be executed by users who have a certain role. I would like to adjust the menus of my front-end AngularJS application so that only if the user has this role then the appropriate links will appear visible. I do realize that this would not stop a user from checking the HTML and posting but I am not concerned about this as I will still have method decoration to limit the ability of users not in a role to perform actions.

Can someone give me an example of how I can do this using just the suite of products mentioned above that I mention in the question plus JavaScript Web Tokens if they help make bring the solution up to date. From what I understand roles are handled by claims but I do not understand how to add these and send them back to the client with tokens. I have done a lot of research on the internet but I've not been able to find any good examples as I think most of this is very new and not many people have had the chance to explore how a SPA can use these very latest software components.

When answering this question please note that I am not looking for an answer that can tell the community how to set up roles on the server or an answer that explains about how important it is to provide role checks on the server. I think almost everyone is aware of this. What I really think will be of use is some very detailed technical suggestions with sample code and an explanation. To keep the answer focused it would probably be of help to everyone if answers that do not meet this need are not posted as suggested answers.

Thank you in advance.

Samantha J T Star
  • 30,952
  • 84
  • 245
  • 427
  • Can you expound on this "roles are handled by claims"? Claims is an alternative of roles but if you still want to use roles, add a claim with the type [ClaimTypes.Role](http://msdn.microsoft.com/en-us/library/system.security.claims.claimtypes.role(v=vs.110).aspx) – LostInComputer May 21 '14 at 15:52
  • I think this might help: http://msdn.microsoft.com/en-us/library/hh545448.aspx – Samantha J T Star May 21 '14 at 15:59

4 Answers4

21

The short answer to your question is ApplicationOAuthProvider.CreateProperties method. Its created for you by default and is found under WebApi2/Provider/ApplicationOAuthProvider.cs, By default it only sends the userName

//WepApi2/Providers/ApplicationOAuthProvider.cs
public static AuthenticationProperties CreateProperties(string userName)
{
    IDictionary<string, string> data = new Dictionary<string, string>
    {
        { "userName", userName }
    };
    return new AuthenticationProperties(data);
}

I would make the following update (in case I need to send more user data later on):

public static AuthenticationProperties CreateProperties(string userName, ClaimsIdentity oAuthIdentity)
{ 
  IDictionary<string, string> data = new Dictionary<string, string>
  {
      { "userName", userName},
      { "roles",string.Join(",",oAuthIdentity.Claims.Where(c=> c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray())}

  };
  return new AuthenticationProperties(data);
}

If you haven't made major changes to the WebApi project, ApplicationOAuthProvider.CreateProperties is only referenced in two places, just update the calling code to pass the oAuthIdentity along with user.UserName and you'll get the user roles sent along with the access token response:

{
 "access_token": "ZpxAZyYuvCaWgShUz0c_XDLFqpbC0-DIeXl_tuFbr11G-5hzBzSUxFNwNPahsasBD9t6mDDJGHcuEqdvtBT4kDNQXFcjWYvFP7U2Y0EvLS3yejdSvUrh2v1N7Ntz80WKe5G_wy2t11eT0l48dgdyak8lYcl3Nx8D0cgwlQm-pePIanYZatdPFP9q5jzhD-_k9SF-ARTHgf0ePnbvhLBi1MCYQjvfgPKlbBHt0M5qjwGAeFg1IhSVj0gb4g9QTXoiPhRmxGBmjOpGgzxXixavmrpM7cCBFLoR3DCGnIJo6pwT-6VArxlB8-ZyyOZqh_6gGtptd0lIu8iJRUIGwO9HFNkROdoE9T4buwLnhPpWpy9geBjPVwsB1K3xnbch26YbklhxIHVybBxeIVXd17QTw_LjlQ5TJdqpAYfiZ5B9Nx2AFYYYe3--aemh4y1XOIvN",
 "token_type": "bearer",
 "expires_in": 1209599,
 "userName": "MK",
 "roles": "Admin,Public",
 ".issued": "Fri, 23 May 2014 17:36:54 GMT",
 ".expires": "Fri, 06 Jun 2014 17:36:54 GMT"
}

Now you have the roles available, you can use Angular conditional directives to show/hide actions according to user roles.

If you need more clarification, please let me know.

Edit:

Decorating your controller methods with Authorize attribute is valid, since the HttpContext.Current.User.Identity is actually a ClaimsIdentity. But as not to hard code security logic inside the application, I prefer using ClaimsAuthorizationManager

public ActionResult Secure()
{
  if(!ClaimsPrincipalPermission.CheckAccess("resource", "action"))
    return new HttpUnauthorizedResult();

  ViewBag.Message = "You are allowed to perform action on resource.";
  return View();
}

Roles creation using RoleManager:

RoleManager roleManger = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>());
roleManager.Create(new IdentityRole() { Name = "Admin" });

Roles assignment using UserManager:

userManager.AddToRole(user.Id, "Admin");
MK.
  • 5,139
  • 1
  • 22
  • 36
  • Thank you. I will check out your answer and update the comments. – Samantha J T Star May 24 '14 at 00:57
  • MK. - Thanks for your advice. I checked out your code sample above and that works fine. However I have a problem in that although my users were assigned roles and they fit in the identity tables these roles are not present as claims. So when I check the users that have had roles assigned then I see that there are no claims from them. In your case above where your user was assigned the roles of Admin and Public, how did you assign the role Admin? Also do you decorate your controller methods with a check to ensure the user is in the Admin role ? – Samantha J T Star May 24 '14 at 15:54
  • @SamanthaJ Roles are present inside `ClaimsIdentity`, I'm using a modified `UserManager`, I updated the answer to work with the default asp.net identity 2. You may also query roles using `user.Roles` directly. As for roles checking I prefer using `ClaimsAuthorizationManager` http://msdn.microsoft.com/en-us/library/system.security.claims.claimsauthorizationmanager.checkaccess(v=vs.110).aspx – MK. May 24 '14 at 17:38
  • As not to confuse the last edit with the previous update, the update concerns `CreateProperties` method (sending `ClaimsIdentity` instead of `IdentityUser`). – MK. May 24 '14 at 20:31
  • Thanks for the update. I tried this and it works very well. It does exactly what I need with just a few lines of code. I will accept this answer. Thanks again for your help. – Samantha J T Star May 25 '14 at 04:06
2

There are 2 ways I see you can approach your problem.

  1. include the "Role" Information to the token by a hash or a simple string append as you are the one generating the token, then you can decipher it on the angular.

  2. it seems you want to use ASP.NET Identity system and store and retrieve the role information there. If that is the case you can go through this post pay attention to "Initialize the database to create Admin Role and Admin User" section.

IMO, #1 will give you more flexibility on how you store and use your user data as #2 you are following Microsoft's IdentityUser , although it look magic sometimes and it tend to post limitation and you need to spend time to understand how it works behind the scene and make it work for your project.

To know more about the "Individual User Accounts" you pick during the WebAPI project you created, you can go to http://www.asp.net/visual-studio/overview/2013/creating-web-projects-in-visual-studio#indauth

DigitalFreak
  • 344
  • 1
  • 8
0

I have a very similar scenario as yours, but instead of using tokens to authenticate, I use an Identity Server (Thinktecture) to handle my authentication. My app redirects to the Identity Server to authenticate and it comes back with some very basic claims (username and email). This happens as soon as someone tries to first browse to the page. Once the user is authenticated and redirected to my app I make another call to the server to get the user's permissions. These permissions are stored inside a Security service (AngularJS) which also exposes a "hasPermissions" method. I then use ng-if to decide if I am going to display certain parts of the page - including menu items. Something to this effect:

var service = {
    currentUser: ...,
    isAuthenticated: function() {
        return ...;
    },
    checkAccess: function(permission) {
        return service.isAuthenticated() ?
            !!(service.currentUser.permissions.indexOf(permission) > -1) : false;
    }
}

Remember that all all the permissions and html elements are visible to anyone who decides to hit the dev tools button and take a peek. You have to do the same checks on the server side before you perform any action. We have a custom Authorization attribute based off of this that checks if the user has the necessary permissions to execute the MVC/WebAPI action before it is executed for simple cases or actually check it within the Action or the HTTP resource before doing anything that needs elevated privileges.

If you want the client to not see any html elements or certain sections of your site you can either point your templates to a MVC action that will authenticate and then return the HTML template or redirect to another page (not the SPA realm) and have them authenticated on the server before the response is served back.

Preview
  • 35,317
  • 10
  • 92
  • 112
mithun_daa
  • 4,334
  • 5
  • 38
  • 50
  • Thanks for your advice but I am looking for a claims based solution that more closely integrates roles and claims. – Samantha J T Star May 22 '14 at 00:30
  • Roles are claims. What mithun_daa is saying is that you need to make either the claims (or, in this example, the permissions which these claims give you) available to the client to act upon – Martin Booth May 22 '14 at 01:09
  • 2
    @Martin - it's the mechanics behind making these claims available to the client that I am looking for more information on. A lot has changed with the latest version of Identity, WebAPI and Oauth2. I even watched a video by the architect of Thinktekture where he mentioned he can no longer recommend using his own product for new developement as Microsoft now has better solutions. Seems like things continue to change and I can find no good examples that demonstrate the best way to do this. I'm hoping for a good example based on the latest versions of software. Thanks – Samantha J T Star May 22 '14 at 05:03
0

Here another answer:

In ApplicationOAuthProvider.cs

  public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

            ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);


            if (user == null)
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }

simply add a custom header!

 context.OwinContext.Response.Headers.Add("Roles", userManager.GetRoles(user.Id).ToArray());
Pinch
  • 4,009
  • 8
  • 40
  • 60