Echiban Echiban - 6 months ago 58
C# Question

Adding additional logic to Bearer authorization

I am attempting to implement OWIN bearer token authorization, and based on this article. However, there's one additional piece of information I need in bearer token that I don't know how to implement.

In my application, I need to deduce from the bearer token user information (say userid). This is important because I don't want an authorized user from being able to act as another user. Is this doable? Is it even the correct approach? If the userid is a guid, then this would be simple. It's an integer in this case.
An authorized user can potentially impersonate another just by guessing / brute force, which is unacceptable.

Looking at this code:

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

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

public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
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[] { "*" });

using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(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);
}
}


I would think that it is possible to override the authorization / authentication to accommodate what I need?

Answer Source

It seems there's something missing in your code.
You're not validating your client.

You should implement ValidateClientAuthentication and check your client's credentials there.

This is what I do:

public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
        string clientId = string.Empty;
        string clientSecret = string.Empty;

        if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) 
        {
            context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
            context.Rejected();
            return;
        }

        ApplicationDatabaseContext dbContext = context.OwinContext.Get<ApplicationDatabaseContext>();
        ApplicationUserManager userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

        if (dbContext == null)
        {
            context.SetError("server_error");
            context.Rejected();
            return;
        }

        try
        {
            AppClient client = await dbContext
                .Clients
                .FirstOrDefaultAsync(clientEntity => clientEntity.Id == clientId);

            if (client != null && userManager.PasswordHasher.VerifyHashedPassword(client.ClientSecretHash, clientSecret) == PasswordVerificationResult.Success)
            {
                // Client has been verified.
                context.OwinContext.Set<AppClient>("oauth:client", client);
                context.Validated(clientId);
            }
            else
            {
                // Client could not be validated.
                context.SetError("invalid_client", "Client credentials are invalid.");
                context.Rejected();
            }
        }
        catch (Exception ex)
        {
            string errorMessage = ex.Message;
            context.SetError("server_error");
            context.Rejected();
        }
  }

A good article full of details can be found here.
A even better explanation can be found in this blog series.

UPDATE:

I did some digging and webstuff is right.

In order to pass errorDescription to the client we need to Rejected before we set the error with SetError:

context.Rejected();
context.SetError("invalid_client", "The information provided are not valid !");
return;

or we can extend it passing a serialized json object in the description:

context.Rejected();
context.SetError("invalid_client", Newtonsoft.Json.JsonConvert.SerializeObject(new { result = false, message = "The information provided are not valid !" }));
return;

enter image description here

With a javascript/jQuery client we could deserialize the text response and read the extended message:

$.ajax({
    type: 'POST',
    url: '<myAuthorizationServer>',
    data: { username: 'John', password: 'Smith', grant_type: 'password' },
    dataType: "json",
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    xhrFields: {
        withCredentials: true
    },
    headers: {
        'Authorization': 'Basic ' + authorizationBasic
    },  
    error: function (req, status, error) {
            if (req.responseJSON && req.responseJSON.error_description)
            {
               var error = $.parseJSON(req.responseJSON.error_description);
                    alert(error.message);
            }
    }
});
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download