Getting "error": "unsupported_grant_type" when trying to get a JWT by calling an OWIN OAuth secured Web Api via Postman

I have followed this article to implement an OAuth Authorization server. However when I use post man to get a token, I get an error in the response:

"error": "unsupported_grant_type"

I read somewhere that the data in Postman needs to be posted using Content-type:application/x-www-form-urlencoded. I have prepped the required settings in Postman:

enter image description here

and yet my headers are like this:

enter image description here

Here is my code

public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}


public override Task MatchEndpoint(OAuthMatchEndpointContext context)
{
if (context.OwinContext.Request.Method == "OPTIONS" && context.IsTokenEndpoint)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "accept", "authorization", "content-type" });
context.OwinContext.Response.StatusCode = 200;
context.RequestCompleted();
return Task.FromResult<object>(null);
}
return base.MatchEndpoint(context);
}


public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
string allowedOrigin = "*";


context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Content-Type" });


Models.TheUser user = new Models.TheUser();
user.UserName = context.UserName;
user.FirstName = "Sample first name";
user.LastName = "Dummy Last name";


ClaimsIdentity identity = new ClaimsIdentity("JWT");


identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
foreach (string claim in user.Claims)
{
identity.AddClaim(new Claim("Claim", claim));
}


var ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
}
}


public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;


public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}


public string Protect(AuthenticationTicket data)
{
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}


public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}

In the CustomJWTFormat class above only the breakpoint in the constructor gets hit. In the CustomOauth class, the breakpoint in the GrantResourceOwnerCredentials method never gets hit. The others do.

The Startup class:

public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);


HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);


ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);


app.UseWebApi(config);
}


private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
var OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["Issuer"])
};


// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}


private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
string issuer = ConfigurationManager.AppSettings["Issuer"];
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);


// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
}

Do I need to set up Content-type:application/x-www-form-urlencoded somewhere else in the web api code? What could be wrong? Please help.

178569 次浏览

The response is a bit late - but in case anyone has the issue in the future...

From the screenshot above - it seems that you are adding the url data (username, password, grant_type) to the header and not to the body element.

Clicking on the body tab, and then select "x-www-form-urlencoded" radio button, there should be a key-value list below that where you can enter the request data

try to add this in your payload

grant_type=password&username=pippo&password=pluto

With Postman, select Body tab and choose the raw option and type the following:

grant_type=password&username=yourusername&password=yourpassword
  1. Note the URL: localhost:55828/token (not localhost:55828/API/token)
  2. Note the request data. Its not in json format, its just plain data without double quotes. userName=xxx@gmail.com&password=Test123$&grant_type=password
  3. Note the content type. Content-Type: 'application/x-www-form-urlencoded' (not Content-Type: 'application/json')
  4. When you use JavaScript to make post request, you may use following:

    $http.post("localhost:55828/token",
    "userName=" + encodeURIComponent(email) +
    "&password=" + encodeURIComponent(password) +
    "&grant_type=password",
    {headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}
    ).success(function (data) {//...
    

See screenshots below from Postman:

Postman Request

Postman Request Header

Use grant_type={ Your password} enter image description here

If you are using AngularJS you need to pass the body params as string:

    factory.getToken = function(person_username) {
console.log('Getting DI Token');
var url = diUrl + "/token";


return $http({
method: 'POST',
url: url,
data: 'grant_type=password&username=myuser@user.com&password=mypass',
responseType:'json',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
};

Old Question, but for angular 6, this needs to be done when you are using HttpClient I am exposing token data publicly here but it would be good if accessed via read-only properties.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { delay, tap } from 'rxjs/operators';
import { Router } from '@angular/router';




@Injectable()
export class AuthService {
isLoggedIn: boolean = false;
url = "token";


tokenData = {};
username = "";
AccessToken = "";


constructor(private http: HttpClient, private router: Router) { }


login(username: string, password: string): Observable<object> {
let model = "username=" + username + "&password=" + password + "&grant_type=" + "password";


return this.http.post(this.url, model).pipe(
tap(
data => {
console.log('Log In succesful')
//console.log(response);
this.isLoggedIn = true;
this.tokenData = data;
this.username = data["username"];
this.AccessToken = data["access_token"];
console.log(this.tokenData);
return true;


},
error => {
console.log(error);
return false;


}
)
);
}
}

I was getting this error too and the reason ended up being wrong call url. I am leaving this answer here, if someone else happens to mix the urls and getting this error. Took me hours to realize I had wrong URL.

Error I got (HTTP code 400):

{
"error": "unsupported_grant_type",
"error_description": "grant type not supported"
}

I was calling:

https://MY_INSTANCE.lightning.force.com

While the correct URL would have been:

https://MY_INSTANCE.cs110.my.salesforce.com

Another common cause of this 'unsupported_grant_type' error is calling the API as GET instead of POST.

In Angular 13, this is what I did;

let headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
})
return this.http.post(this.url + 'token', 'grant_type=password&username='+form.username+'&password='+form.password, { headers: headers})