'How to implement a client_credentials grant type in an angular http request?
I've created an OAUTH2 authorization server which uses client credentials for authentication and is responsible for the issuing of JWT tokens. When I place my request using postman I get the JWT token as expected, but when I place the request in angular I am received the error message: "unsupported_grant_type".
Here is the relevant code for the authorization server:
Startup.cs
public void Configuration(IAppBuilder app)
{
// HTTP Configuration
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
ConfigureOAuth(app);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth2/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new CustomOAuthProvider(ActiveKernel),
AccessTokenFormat = new CustomJwtFormat("http://localhost:62790", ActiveKernel)
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
CustomOAuthProvider.cs
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
Guid clientIdGuid;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (null == context.ClientId || null == clientSecret || !Guid.TryParse(clientId, out clientIdGuid))
{
context.SetError("invalid_credentials", "A valid client_Id and client_Secret must be provided");
return Task.FromResult<object>(null);
}
// change to async
var client = _clientRepo.GetClient(clientIdGuid, clientSecret);
if (client == null)
{
context.SetError("invalid_credentials", "A valid client_Id and client_Secret must be provided");
return Task.FromResult<object>(null);
}
context.Validated();
return Task.FromResult<object>(0);
}
public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
Guid clientId;
Guid.TryParse(context.ClientId, out clientId);
var client = _clientRepo.GetByClientId(clientId);
if (client == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("fakeClaim", client.someField.ToString()));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"audience", (context.ClientId == null) ? string.Empty : context.ClientId
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
When I place a request to http://localhost:55555/oauth2/Token using the Authorization type Oauth2 in postman, I am returned a valid JWT token which I am able to add to my requests:
However, when I try to obtain an access token using angular I receive an error message indicating: "unsupported_grant_type". My desired grant_type is client_credentials. I've tried multiple approaches in angular to get this to work with no luck. Here is the current request in Angular:
function getLoginToken() {
var auth = btoa("89C30F1E-DEE3-4C67-8P2E-9C974R5A35EA:B9wXE8Vo+FEkm2AnqFZlS+KJiwYc+bSnarpq90lGyBo=");
var loginToken = $http({
url: 'http://localhost:55555/oauth2/Token',
method: "POST",
data: {
grant_type: 'client_credentials'
},
withCredentials: true,
headers: {
"Authorization": "Basic " + auth,
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(function(data) {
return data;
}).catch(function (errorResponse) {
throw errorResponse;
});
return loginToken;
};
I've tried adding 'Content-Type': 'application/x-www-form-urlencoded' to the headers and adding the grant_type to the URL but the result is the same. If I use postman to place the request without using the Authorization feature; but instead using the Body 'x-www-form-urlencoded' I receive the same error message as in angular.
In both the angular and second postman examples I am actually getting the correct client_id and client_secret inside of the ValidateClientAuthentication method and the context is validated.
Here are the fiddler images of the two requests:
Any suggestions on what may be going wrong here and how I may be able to resolve the issue?
Thanks in advance!
Solution 1:[1]
I've resolved the issue by adding the following lines of code:
$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8";
var payload = $.param({ grant_type: 'client_credentials' });
The grant_type now comes through as the key and the 'client_credentials' are the value. The entire requests looks like this:
function getLoginToken() {
$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8";
var payload = $.param({ grant_type: 'client_credentials' });
var auth = btoa("89C30F1E-DEE3-4C67-8P2E-9C974R5A35EA:B9wXE8Vo+FEkm2AnqFZlS+KJiwYc+bSnarpq90lGyBo=");
var loginToken = $http({
url: 'http://localhost:55555/oauth2/Token',
method: "POST",
data: payload,
withCredentials: true,
headers: {
"Authorization": "Basic " + auth,
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(function(data) {
return data;
}).catch(function (errorResponse) {
throw errorResponse;
});
return loginToken;
};
I hope this helps someone down the line!
Solution 2:[2]
To be even more clear the raw request minimum has to be:
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=USERNAME&password=PASSWORD
Solution 3:[3]
I believe that client_credentials grant type which you used to implement in frontend is not recommended. Just using for postman test.
they refer using authorize_code with PCKE to protect your client secrets. ie: OAuth 2.0 Authorization Code with PKCE Flow
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Zoop |
Solution 2 | Nick Kovalsky |
Solution 3 | tanho |