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?
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 .
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With