'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!



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