iphoneobjective-coauth-2.0nsurlconnection

Invalid grant_type parameter or parameter missing on POST for requesting access token


I am struggling to get the access token on Quizlet (oauth2). Everything works fine so far, I can make the user accepting my app on Quizlet, get redirected, but when requesting the access token via NSURLConnection, I always get the following error:

2013-08-17 09:39:33.422 Abiliator[49549:c07] Returned data in string format: {"http_code":400,"error":"invalid_request","error_title":"Not Allowed","error_description":"Invalid grant_type parameter or parameter missing"}

Here the code for the user authentication (must be via browser according to spec):

- (void) authenticateQuizletUser
{

NSString *quizletRandomString = [abiliatorAppDelegate GetUUID];
NSString *authURLString = [@"https://quizlet.com/authorize/?response_type=code&client_id=" stringByAppendingString:@"<myID>&scope=read"];
authURLString = [authURLString stringByAppendingString:@"&state="];
authURLString = [authURLString stringByAppendingString:quizletRandomString];
authURLString = [authURLString stringByAppendingString:@"&redirect_uri=Abiliator://after_oauth"];

NSLog(@"Authentication URL sent: %@", authURLString);
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: authURLString]];

}

That works fine, as I mentioned. The app starts Safari and the user must acknowledge the request entering user id and password and the server redirects into my app, which I catch in the method below, which then throws the error described.

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if (!url) {  return NO; }
NSString *URLString = [url absoluteString];
NSLog(@"Received URL: %@", URLString);
NSString *myURLQuery = [url query];
NSString *myAuthCode = [self getAuthorizationCodeFromURL:myURLQuery];
NSLog(@"Component1: %@", myAuthCode);
NSString *authPasswd = @"myPasswd";
NSString *username=@"myUserName";

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api.quizlet.com/oauth/token"]];

request.HTTPMethod = @"POST";

[request setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"Abiliator://after_oauth" forHTTPHeaderField:@"redirect_uri"];

// According to Quizlet API doc: You must set this (grant_type) to the string "authorization_code". 
[request setValue:@"authorization_code" forHTTPHeaderField:@"grant_type"];
[request setValue:myAuthCode forHTTPHeaderField:@"code"];

NSString *authStr = [NSString stringWithFormat:@"%@:%@", username, authPasswd];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]];
[request setValue:authValue forHTTPHeaderField:@"Authorization"];

NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];


return YES; }

Any help hihgly appreciated.


Solution

  • OAuth2 defines 4 ways to gain an access token. The most secure and most complicated is the Authorization Code Grant that is used by Quizlet as described here.

    The Authorization Code Grant consists of two steps:

    You did the first call right. The problem with the second call is that you put the grant_type parameter in the wrong place of your request. In this line you treat it as an HTTP header:

    [request setValue:@"authorization_code" forHTTPHeaderField:@"grant_type"];
    

    And here you also treat the authorization code as an HTTP header:

    [request setValue:myAuthCode forHTTPHeaderField:@"code"];
    

    But OAuth2 requires you to put both into the body of your request. Here is an example of a correct request:

    POST /oauth/token/ HTTP/1.1
    Content-Type: application/x-www-form-urlencoded;charset=UTF-8
    Content-Length: 999
    Authorization: Basic xxx
    
    grant_type=authorization_code&code=theCodeYouGotInTheFirstStep&
    scope=somescope&redirect_uri=theSameUriYouIncludedEarlier
    

    (The stuff below the empty line is your request's body)
    (I added the linebreak in the body only for readability - you must not include it in a request)

    Bonus answer: Please keep in mind that OAuth2 is insecure by default: If you don't do a bit of extra work, your app is vulnerable to Cross-Site Request Forgery attacks as even mentioned in the OAuth2 RFC. To prevent this OAuth2 offers you the state parameter. You have to generate a non-guessable value for your state and include it in the first request. You must not fire the second request, if the state returned by the server is not the same you generated earlier.