Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AFOAuth2Client unable to access resource

Using AFOAuth2Client and AFNetworking on iOS 6 I am able to get an access token, but am unable to access a resource, the server responds with a status code of 401 unauthorized. This is against a custom Rails 3 API backend using doorkeeper as the OAuth provider. The following client ruby code, using the OAuth2 gem, works OK:

client = OAuth2::Client.new(app_id, secret, site: "http://subdomain.example.com/")
access_token = client.password.get_token('username', 'password')
access_token.get('/api/1/products').parsed

The iOS code is as below, in the login button handler I authenticate using the username and password, and store the credentials:

- (IBAction)login:(id)sender {
    NSString *username = [usernameField text];
    NSString *password = [passwordField text];

    NSURL *url = [NSURL URLWithString:kClientBaseURL];
    AFOAuth2Client *client = [AFOAuth2Client clientWithBaseURL:url clientID:kClientID secret:kClientSecret];

    [client authenticateUsingOAuthWithPath:@"oauth/token"
                              username:username
                              password:password
                                 scope:nil
                               success:^(AFOAuthCredential *credential) {
                                   NSLog(@"Successfully received OAuth credentials %@", credential.accessToken);
                                   [AFOAuthCredential storeCredential:credential
                                                       withIdentifier:client.serviceProviderIdentifier];
                                   [self performSegueWithIdentifier:@"LoginSegue" sender:sender];
                               }
                               failure:^(NSError *error) {
                                   NSLog(@"Error: %@", error);
                                   [passwordField setText:@""];
                               }];
}

and I've subclassed AFHTTPClient for my endpoint and in initWithBaseURL it retrieves the credentials and sets the authorization header with the access token:

- (id)initWithBaseURL:(NSURL *)url {
    self = [super initWithBaseURL:url];
    if (!self) {
        return nil;
    }

    [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
    [self setDefaultHeader:@"Accept" value:@"application/json"];

    AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:@"subdomain.example.com"];
    [self setAuthorizationHeaderWithToken:credential.accessToken];

    return self;
}

Is this the correct way to use AFOAuth2Client and AFNetworking? And any idea why this is not working?

like image 806
ChrisR Avatar asked Jan 05 '13 23:01

ChrisR


People also ask

How can I get access token authorization code?

To get a new access token, use the refresh token as you would an authorization code, but with a grant_type value of refresh_token and a refresh_token parameter that holds the contents of the refresh token. The type of grant being used. To exchange a refresh token for an access token, use refresh_token .

How do I get the access token from refresh token?

To get a refresh token , you must include the offline_access scope when you initiate an authentication request through the /authorize endpoint. Be sure to initiate Offline Access in your API.


1 Answers

Managed to get this working by changing:

    AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:@"subdomain.example.com"];
    [self setAuthorizationHeaderWithToken:credential.accessToken];

to:

    AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:@"subdomain.example.com"];
    NSString *authValue = [NSString stringWithFormat:@"Bearer %@", credential.accessToken];
    [self setDefaultHeader:@"Authorization" value:authValue];

UPDATE

What I had failed to notice was that AFOAuth2Client is itself a subclass of AFHTTPClient so can be used as the base class of the API class, e.g.:

@interface YFExampleAPIClient : AFOAuth2Client

    + (YFExampleAPIClient *)sharedClient;

    /**

     */
    - (void)authenticateWithUsernameAndPassword:(NSString *)username
                                       password:(NSString *)password
                                        success:(void (^)(AFOAuthCredential *credential))success
                                        failure:(void (^)(NSError *error))failure;

    @end

And the implementation becomes:

@implementation YFExampleAPIClient

+ (YFExampleAPIClient *)sharedClient {
    static YFExampleAPIClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURL *url = [NSURL URLWithString:kClientBaseURL];
        _sharedClient = [YFExampleAPIClient clientWithBaseURL:url clientID:kClientID secret:kClientSecret];
    });

    return _sharedClient;
}

- (void)authenticateWithUsernameAndPassword:(NSString *)username
                                   password:(NSString *)password
                                    success:(void (^)(AFOAuthCredential *credential))success
                                    failure:(void (^)(NSError *error))failure {
    [self authenticateUsingOAuthWithPath:@"oauth/token"
                                  username:username
                                  password:password
                                     scope:nil
                                   success:^(AFOAuthCredential *credential) {
                                       NSLog(@"Successfully received OAuth credentials %@", credential.accessToken);
                                       [self setAuthorizationHeaderWithCredential:credential];
                                       success(credential);
                                   }
                                   failure:^(NSError *error) {
                                       NSLog(@"Error: %@", error);
                                       failure(error);
                                   }];
}

- (id)initWithBaseURL:(NSURL *)url
             clientID:(NSString *)clientID
               secret:(NSString *)secret {
    self = [super initWithBaseURL:url clientID:clientID secret:secret];
    if (!self) {
        return nil;
    }

    [self setDefaultHeader:@"Accept" value:@"application/json"];

    return self;
}

@end

Note that initWithBaseURL is overridden to set the HTTP accept header.

Full source code is available on GitHub - https://github.com/yellowfeather/rails-saas-ios

like image 101
ChrisR Avatar answered Sep 25 '22 02:09

ChrisR