4

I am trying to get this working, but no luck so far. My Authorize attribute works on its own, but once i state a Role that the user must be apart of, then i get the following message returned from my webapi

{"message":"Authorization has been denied for this request."}

Startup.cs

public void Configuration(IAppBuilder app)
{
    //HttpConfiguration config = new HttpConfiguration();

    ConfigureOAuth(app);

    // simple injector
    SimpleInjectorConfig.Register();

    AutoMapperConfig.RegisterMappings();
    AreaRegistration.RegisterAllAreas();
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
}

public void ConfigureOAuth(IAppBuilder app)
{
    OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = new SimpleAuthorizationServerProvider(new AccountService(new DataContext()))
    };

    // Token Generation
    app.UseOAuthAuthorizationServer(OAuthServerOptions);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

}

SimpleAuthorizationServerProvider.cs

public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    private AccountService _service;

    public SimpleAuthorizationServerProvider(AccountService service)
    {
        _service = service;
    }

    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
    }


    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {

        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        User user = await _service.FindUserAsync(context.UserName, context.Password);

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


        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim("sub", context.UserName));
        //identity.AddClaim(new Claim("role", "user"));

        context.Validated(identity);

    }
}

Decorating a controller method, i use

[Authorize(Roles="CanViewCompany")]

If i use just this, it works and returns the data i would expect

[Authorize]

My basic User class

public class User : IdentityUser<long, UserLogin, UserRole, UserClaim>, IUser<long>
{
    public User()
        : base()
    {
        this.Groups = new HashSet<UserGroup>();
    }

    public virtual ICollection<UserGroup> Groups { get; set; }

}

I am sure i am missing something, but i just aint sure what. Any help much appreciated

Gillardo
  • 9,518
  • 18
  • 73
  • 141
  • 1
    Debug your Web API and watch your user object. Make sure it's 1) the user you think it should be and 2) that it actually has the role you think it should. One of those is not the case. – Chris Pratt Nov 10 '14 at 15:50
  • Do you know what class/method/function is called, where the token is converted and the identity is retrieved? – Gillardo Nov 10 '14 at 16:15
  • You won't be able to step into that. Just inspect the value inside your action. – Chris Pratt Nov 10 '14 at 16:23

3 Answers3

3

it seems to be this function that was causing the error. I was not setting the username (which didnt make it break, but the User property didnt have one, which i am sure i will need in future).

Also i was not adding any roles. I am not sure if this is the right way to fix this, but it works.

If someone knows of a better/right way to do this, let me know.

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {

        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        User user = await _service.FindUserAsync(context.UserName, context.Password);

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

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        identity.AddClaim(new Claim(ClaimTypes.Role, "CanViewCompany"));
        identity.AddClaim(new Claim(ClaimTypes.Role, "CanAddCompany"));
        identity.AddClaim(new Claim(ClaimTypes.Role, "CanEditCompany"));
        identity.AddClaim(new Claim(ClaimTypes.Role, "CanDeleteCompany"));

        context.Validated(identity);

    }
Gillardo
  • 9,518
  • 18
  • 73
  • 141
1

This won't scale if you have newer roles, you need to create this dynamically, please check my answer to this SO question which is very identical for your case but check how you get user roles from DB not to hard-code them, what you are currently doing is assigning the same roles for all authenticated users which makes no sense.

I guess this code is taken from http://bitoftech.net posts about authorization, if this is the case then please remove the below LOC which dose not make sense in your case as well.

 identity.AddClaim(new Claim("sub", context.UserName));
Community
  • 1
  • 1
Taiseer Joudeh
  • 8,953
  • 1
  • 41
  • 45
  • I do want the roles from yhe database ideally but not sure where or when to do this. Plus i have loylts more roles/permissions and the access token size is increasing alot. I will check ur other answer thanks – Gillardo Nov 10 '14 at 19:28
  • Checked ur answer.. Arent you saying to do it like i have stated in the answer to this question? – Gillardo Nov 10 '14 at 19:33
  • Go with the dynamic way which loads the roles from DB not to statically assign them, but watch your token size if you have multiple roles. – Taiseer Joudeh Nov 10 '14 at 20:18
  • I have only hard-coded them here for testing. I have about 50 roles, which is going to make the token size far to big. Do you know where i can intercept the request and load my identity using the username instead? or is a large token size ok? It doesnt seem right to me... – Gillardo Nov 11 '14 at 14:00
  • I do not know what is the business req. you are trying to achieve but 50 roles? Hmm sounds strange. Yes the token size will be so big and it is not feasible to transmit it with every request. – Taiseer Joudeh Nov 12 '14 at 10:42
  • I am using permissions instead of roles but thought i could load the permissions into the roles against an identity so i coyld use the authorise attribute etc – Gillardo Nov 12 '14 at 11:35
0

I think that you have a little problem with your aproach. Any user will have all roles...

My solution was as follows:

1) To obtain the claims array through the CreateIdentityAsync function:

    var claims = await _service.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

2) Add the claims array to the identity object:

identity.AddClaims(claims.Claims);

Displaying the code together...

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {

        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        User user = await _service.FindUserAsync(context.UserName, context.Password);

        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }
        var claims = await _service.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaims(claims);

        context.Validated(identity);
    }

That's all.

jasalvador
  • 43
  • 4