Zoop Zoop - 5 months ago 72
AngularJS Question

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:

Postman Request

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:

Broken request from Angular:
Broken angular request

Working postman request:
Working Postman Request

Any suggestions on what may be going wrong here and how I may be able to resolve the issue?

Thanks in advance!

Answer Source

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!