nxtbgthng / OAuth2Client

Client library for OAuth2 (currently built against draft 10 of the OAuth2 spec)
855 stars 217 forks source link

FitBit: Authorization header required #200

Open chrisfoulds opened 8 years ago

chrisfoulds commented 8 years ago

I am trying to connect to Fitbit in an iOS App https://dev.fitbit.com/docs/oauth2/#authorization-page

I have put this code in the app startup

    [[NXOAuth2AccountStore sharedStore] setClientID:@"xxxxx"
                                             secret:@"yyyyyyyy"
                                            scope:[NSSet setWithObjects:@"activity", @"heartrate", @"weight",nil]
                                   authorizationURL:[NSURL URLWithString:@"https://www.fitbit.com/oauth2/authorize"]
                                           tokenURL:[NSURL URLWithString:@"https://api.fitbit.com/oauth2/token"]
                                        redirectURL:[NSURL URLWithString:@"myapp://handleOauthLogin"]
                                      keyChainGroup:@""
                                          tokenType:@"Bearer"
                                     forAccountType:@"fitbit"];

    NSMutableDictionary *configuration = [NSMutableDictionary dictionaryWithDictionary:[[NXOAuth2AccountStore sharedStore] configurationForAccountType:@"fitbit"]];
    NSDictionary *customHeaderFields = [NSDictionary dictionaryWithObject:@"application/x-www-form-urlencoded" forKey:@"Content-Type"];
    [configuration setObject:customHeaderFields forKey:kNXOAuth2AccountStoreConfigurationCustomHeaderFields];
    [[NXOAuth2AccountStore sharedStore] setConfiguration:configuration forAccountType:@"fitbit"];

In my callback I do

[[NXOAuth2AccountStore sharedStore] handleRedirectURL: url];

and I invoke the auth using :

[[NXOAuth2AccountStore sharedStore] requestAccessToAccountWithType:@"fitbit"];

and I have the notification handlers that return as failure.

The app launches, safari, I login , say everything is ok, it then asks to return to my app. I say ok, it returns but the notification from storing the token says

oauthConnection Error: {"errors":[{"errorType":"invalid_request","message":"Authorization header required. Visit https://dev.fitbit.com/docs/oauth2 for more information on the Fitbit Web API authorization process."}],"success":false}

I just can not see what I have missed, any help appreciated.

chrisfoulds commented 8 years ago

So managed to get it working with a hack

In NXOauth2Connection.m / - (NSURLConnection *)createConnection;

I added in :

    NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", client.clientId, client.clientSecret];
    [startRequest setValue:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)] forHTTPHeaderField:@"Authorization"];
jwlondon98 commented 8 years ago

@chrisfoulds hey i had the same problem as you although your solution did not help me. do you think you could send your code

chrisfoulds commented 8 years ago

That is it ! - all the code I had to add was those two lines. I can't give the entire source file as it's under copyright

jwlondon98 commented 8 years ago

@chrisfoulds oh, i see.. I'm extremely new to github and oauth so any help would be very useful. I believe the error that I am still getting (the same one you got) has to do with the NXOAuth2Connect.m file. I may not have placed the code you used to hack it in the correct spot but its in the createConnetion method. All I can think of is that there is a bit of code in that method that has been deprecated to NSURLSession. I believe everything else I have is correct I just get the error 'Authorization header required' whenever I make a GET request.

//This is the code in my AppDelegate.m file
//OAuth config
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[NXOAuth2AccountStore sharedStore] setClientID:@"XxXxXx"
                                             secret:@"XxXxXxXxXx"
                                              scope:[NSSet setWithObjects:@"activity", @"heartrate", @"location", @"nutrition", @"profile", @"settings", @"sleep", @"social", @"weight", nil]
                                   authorizationURL:[NSURL URLWithString:@"https://www.fitbit.com/oauth2/authorize?response_type=token&client_id=227NJP&redirect_uri=myapp://handleOauthLogin&scope=activity%20heartrate%20location%20nutrition%20nutrition%20profile%20settings%20sleep%20social%20weight&expires_in=2592000"]
                                           tokenURL:[NSURL URLWithString:@"https://api.fitbit.com/oauth2/access_token"]
                                        redirectURL:[NSURL URLWithString:@"myapp://handleOauthLogin"]
                                      keyChainGroup:@""
                                          tokenType:@"Bearer"
                                     forAccountType:@"fitbit"];

    NSMutableDictionary *configuration = [NSMutableDictionary dictionaryWithDictionary:[[NXOAuth2AccountStore sharedStore] configurationForAccountType:@"fitbit"]];

    NSDictionary *customHeaderFields = [NSDictionary dictionaryWithObject:@"application/x-www-form-urlencoded" forKey:@"Content-Type"];

    [configuration setObject:customHeaderFields forKey:kNXOAuth2AccountStoreConfigurationCustomHeaderFields];

    [[NXOAuth2AccountStore sharedStore] setConfiguration:configuration forAccountType:@"fitbit"];

    [[NXOAuth2AccountStore sharedStore] requestAccessToAccountWithType:@"fitbit"];

    return YES;
}

//Callback
- (BOOL) application:(UIApplication *)app openURL:(NSURL *)url options: (NSDictionary<NSString *, id> *)options
{
    NSLog(@"We received a callback");
    return [[NXOAuth2AccountStore sharedStore] handleRedirectURL:url];
}
//This is the code in my ViewController.m file
//I have two buttons - one handling login, the other using a GET to access a profile
- (IBAction)fitbitBtn:(id)sender
{
    [[NXOAuth2AccountStore sharedStore]
     requestAccessToAccountWithType:@"Fitbit"];
    NSLog(@"LOGIN");
}

- (IBAction)getData:(id)sender
{
    NSLog(@"GET DATA");

    NXOAuth2Account *account = [[NXOAuth2AccountStore sharedStore] accountWithIdentifier:@"fitbit"];

    [NXOAuth2Request performMethod:@"GET"
                        onResource:[NSURL URLWithString:@"https://api.fitbit.com/1/user/-/profile.json"]
                   usingParameters:nil
                       withAccount: account
               sendProgressHandler:nil
                   responseHandler:
     ^(NSURLResponse *response, NSData *responseData, NSError *error){
         NSLog(@"dataAsString %@", [NSString stringWithUTF8String:[responseData bytes]]);
     }];
}
//Here is the code in my NXOAuth2Connect.m file under -(NSURLConnection *)createConnection
- (NSURLConnection *)createConnection;
{
    // if the request is a token refresh request don't sign it and don't check for the expiration of the token (we know that already)
    NSString *oauthAuthorizationHeader = nil;
    if (client.accessToken &&
        ![[requestParameters objectForKey:@"grant_type"] isEqualToString:@"refresh_token"]) {

        // if token is expired don't bother starting this connection.
        NSDate *tenSecondsAgo = [NSDate dateWithTimeIntervalSinceNow:(-10)];
        NSDate *tokenExpiresAt = client.accessToken.expiresAt;
        if (client.accessToken.refreshToken && [tenSecondsAgo earlierDate:tokenExpiresAt] == tokenExpiresAt) {
            [self cancel];
            [client refreshAccessTokenAndRetryConnection:self];
            return nil;
        }

        NSString *tokenType = client.accessToken.tokenType;
        if (tokenType == nil) {
            tokenType = client.tokenType;
        }
        if (tokenType == nil) {
            tokenType = @"OAuth";
        }

        oauthAuthorizationHeader = [NSString stringWithFormat:@"%@ %@", tokenType, client.accessToken.accessToken];
    }

    NSMutableURLRequest *startRequest = [request mutableCopy];
    [self applyParameters:requestParameters onRequest:startRequest];

    if (oauthAuthorizationHeader) {
        [startRequest setValue:oauthAuthorizationHeader forHTTPHeaderField:@"Authorization"];
    }

    if (client.userAgent && ![startRequest valueForHTTPHeaderField:@"User-Agent"]) {
        [startRequest setValue:client.userAgent forHTTPHeaderField:@"User-Agent"];
    }

    if (client.acceptType) {
        [startRequest setValue:client.acceptType forHTTPHeaderField:@"Accept"];
    }

    //This next line gives the following warning - 'initWithRequest:delegate:startImmediately:' is deprecated: first deprecated in iOS 9.0 - Use NSURLSession (see NSURLSession.h)
    //Not sure if that is part of the problem
    NSURLConnection *aConnection = [[NSURLConnection alloc] initWithRequest:startRequest delegate:self startImmediately:NO];
    [aConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [aConnection start];

    if (!sendConnectionDidEndNotification) [[NSNotificationCenter defaultCenter] postNotificationName:NXOAuth2ConnectionDidStartNotification object:self];
    sendConnectionDidEndNotification = YES;

    //Here is the code you previously provided.. not sure if i placed it in the correct area
    NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", client.clientId, client.clientSecret];
    [startRequest setValue:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)] forHTTPHeaderField:@"Authorization"];

    NSLog(@"%@", aConnection);

    return aConnection;
}
jwlondon98 commented 8 years ago

@chrisfoulds also, not sure if this helps but if I move the two lines you provided in the 'NXOAuth2Connection.m' file up right before the NSURLConnection *aConnection bit I get the following error..

{"errors":[{"errorType":"invalid_client","message":"Invalid authorization header. Client id invalid. Visit https://dev.fitbit.com/docs/oauth2 for more information on the Fitbit Web API authorization process."}],"success":false}oper/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/Accounts.framework/Accounts

Like I said I'm not sure if I have your code in the correct location but I suppose that error shows that its a problem with the encoding for the clientID. I honestly have no clue what to do :(

chrisfoulds commented 8 years ago

Your error is different to mine... however So I just took a look at the code and my notes, totally forget I dropped this Oauth2 client as I could never get it to work. I used https://github.com/AFNetworking/AFOAuth2Manager and was up and running in less than a day. You do have to add the Bearer credential on each request (one line of code) but that is it.

jwlondon98 commented 8 years ago

@chrisfoulds ok thank you very much I will look into using AFNetworking

paradaernesto commented 7 years ago

Hello, I had the same error. I added the next lines to fix this:

NSMutableDictionary *configuration = [NSMutableDictionary dictionaryWithDictionary:[[NXOAuth2AccountStore sharedStore] configurationForAccountType:@"fitbit"]];

NSString *authHeader = [NSString stringWithFormat:@"Basic %@", [[NSString stringWithFormat:@"%@:%@", kClientId, kClientSecretKey] base64EncodedString]];

[configuration setObject:@{@"Content-Type": @"application/x-www-form-urlencoded", @"Authorization": authHeader} forKey:kNXOAuth2AccountStoreConfigurationCustomHeaderFields];

[[NXOAuth2AccountStore sharedStore] setConfiguration:configuration forAccountType:@"fitbit"];