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?
The token is a text string, included in the request header. In the request Authorization tab, select Bearer Token from the Type dropdown list. In the Token field, enter your API key value. For added security, store it in a variable and reference the variable by name.
To send a request with the Bearer Token authorization header, you need to make an HTTP request and provide your Bearer Token with the "Authorization: Bearer {token}" header. A Bearer Token is a cryptic string typically generated by the server in response to a login request.
The Basic and Digest authentication schemes are dedicated to the authentication using a username and a secret (see RFC7616 and RFC7617). The Bearer authentication scheme is dedicated to the authentication using a token and is described by the RFC6750.
Bearer Tokens are the predominant type of access token used with OAuth 2.0. A Bearer Token is an opaque string, not intended to have any meaning to clients using it. Some servers will issue tokens that are a short string of hexadecimal characters, while others may use structured tokens such as JSON Web Tokens.
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;
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);
}
}
});
On a side note, if you want to set a custom error message you'll have to swap the order of the context.Rejected
and context.SetError
.
// Summary:
// Marks this context as not validated by the application. IsValidated and HasError
// become false as a result of calling.
public virtual void Rejected();
If you place context.Rejected
after context.SetError
then the property context.HasError
will be reset to false therefore the correct way to use it is:
// Client could not be validated.
context.Rejected();
context.SetError("invalid_client", "Client credentials are invalid.");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With