I'm using AFHTTPClient from AFNetworking to make a call from my IOS app to my server, which is using Django with TastyPie. It's working great when I turn authentication off on the server side; however, when I require authentication and insert the proper username and password into my code, the I receive the following 401 authentication error:
\2012-09-16 00:24:37.877 RESTtest[76909:f803]
Complex AF: Error Domain=AFNetworkingErrorDomain Code=-1011
"Expected status code in (200-299), got 401"
UserInfo=0x686ba00 {AFNetworkingOperationFailingURLResponseErrorKey=<NSHTTPURLResponse: 0x686f130>,
NSErrorFailingURLKey=http://127.0.0.1:8000/api/v1/shoppinglist,
NSLocalizedDescription=Expected status code in (200-299), got 401,
AFNetworkingOperationFailingURLRequestErrorKey=<NSMutableURLRequest http://127.0.0.1:8000/api/v1/shoppinglist>}
Here is my code:
AFAPIClient.h
#import "AFHTTPClient.h"
#interface AFAPIClient : AFHTTPClient
-(void)setUsername:(NSString *)username andPassword:(NSString *)password;
+ (AFAPIClient *)sharedClient;
#end
AFAPIClient.m:
#import "AFAPIClient.h"
#import "AFJSONRequestOperation.h"
static NSString * const baseURL = #"http://#127.0.0.1:8000/api/v1";
#implementation AFAPIClient
+ (AFAPIClient *)sharedClient {
static AFAPIClient *_sharedClient = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
_sharedClient = [[AFAPIClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
//[_sharedClient setAuthorizationHeaderWithUsername:#"myusername" password:#"mypassword"]; I tried putting the authorization command here
});
return _sharedClient;
};
#pragma mark - Methods
-(void)setUsername:(NSString *)username andPassword:(NSString *)password;
{
[self clearAuthorizationHeader];
[self setAuthorizationHeaderWithUsername:username password:password];
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setParameterEncoding:AFJSONParameterEncoding];
//[self setAuthorizationHeaderWithUsername:#"myusername" password:#"mypassword"]; I also tried putting the authorization command here
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
return self;
}
#end
TQViewController.h:
[...]
- (IBAction)sendAFClientRequest:(id)sender {
//[[AFAPIClient sharedClient] setUsername:#"myusername" andPassword:#"mypassword"];
[[AFAPIClient sharedClient] getPath:#"shoppinglist" parameters:nil success:^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Complex AF: %#", [response valueForKeyPath:#"objects"]);
} failure:^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Complex AF: %#", response);
}
];
}
[...]
I know this isn't a problem with my server or my username/password, as I can authenticate just fine by inserting the username/password into the URL:
#"http://myusername:mypassword#127.0.0.1:8000/api/v1/shoppinglist"
Any help on this would be great. It would be wonderful to be able to use AFHTTPClient without inserting the authentication information directly into the static base URL, which seems completely improper. Thanks in advance!
Based on this: https://github.com/AFNetworking/AFNetworking/issues/426
I override the - (void)getPath:(NSString *)path parameters... method in the AFHTTPClient
subclass to look something like this:
- (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:#"GET" path:path parameters:parameters];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
[operation setAuthenticationChallengeBlock:^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge) {
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
[challenge.sender useCredential:newCredential forAuthenticationChallenge:challenge];
}];
[self enqueueHTTPRequestOperation:operation];
}
It only adds the authentication challenge block to the AFHTTPRequestOpertaion, the rest is the same as the original implementation https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFHTTPClient.m
Related
I am using the below code below to perform my webservice calls with the service.I used AFNetworking version below 2.0 where AFHTTPClient .Now i migrated to latest version of AFNetworking .I donot find the AFHTTPClient class in the latest version . What should i replace with the curent code so that it works again .Any help please
#interface APIClient : AFHTTPClient
+ (APIClient*)client;
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock;
#end
// Singleton method
+ (APIClient*)client {
static APIClient *client = nil;
static dispatch_once_t onceInst;
dispatch_once(&onceInst, ^{
client = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObjects:
#"application/json",
#"text/json",
#"text/javascript",
#"text/plain",
#"text/html",
#"application/x-www-form-urlencoded", nil]];
});
return client;
}
#pragma mark - Init
// Intialize the API class with the destination host name
- (APIClient*)init {
self = [super init]; // call super init
if (self != nil) {
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
#pragma mark - Core API Methods
// This function sends an API call to the server
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock {
[MBMNetworkActivity pushNetworkActivity];
NSMutableURLRequest *apiRequest = [self requestWithMethod:#"POST" path:method parameters:params];
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// success! :)
[MBMNetworkActivity popNetworkActivity];
successBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure! :(
[MBMNetworkActivity popNetworkActivity];
failureBlock(error);
}];
[operation start];
}
You can use NSURLSession for quite a bunch of the AFHTTPClient Stuff.
But to achieve all functionality just write a class like you now did but based on NSObject.
NSURLSession has a really nice API and great functionality combined with it.
AFHTTPRequestOperationManager is the replacement class to subclass instead of AFHTTPClient. It's not the same but it's probably what your looking for.
I would suggest you read Mattt Thompson's blog NSHipster. He is the author of AFNetworking and covered the changes a while back http://nshipster.com/afnetworking-2/. There is also an AFNetworking 2.0 migration guide https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide that will be usefull to you.
Finally i was able to do with following changes replacing AFHttpClient with AFHTTPRequestOperationManager
typedef void (^APIClientSuccessCallback) (id response);
typedef void (^APIClientFailureCallback) (id error);
#interface APIClient : AFHTTPRequestOperationManager
+ (APIClient*)client;
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock;
#end
#import "APIClient.h"
#implementation APIClient
+ (APIClient*)client {
static APIClient *client = nil;
static dispatch_once_t onceInst;
dispatch_once(&onceInst, ^{
client = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
client.responseSerializer = [AFJSONResponseSerializer serializer];
[client.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
});
return client;
}
#pragma mark - Core API Methods
// This function sends an API call to the server
- (void)commandWithMethod:(NSString *)method params:(NSMutableDictionary*)params success:(APIClientSuccessCallback)successBlock failure:(APIClientFailureCallback)failureBlock {
[self POST:method parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"response --- %#",responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error ----- %#",error);
}];
}
#end
I was trying to implement an AFNetworking client by subclassing AFHTTPClient and setting base path
#define BaseURLString #"http://company.com/api/"
#implementation WineAPIClient
+(id)sharedInstance{
static APIClient *__sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedInstance = [APIClient alloc]initWithBaseURL:[NSURL URLWithString:BaseURLString]];
});
return __sharedInstance;
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if(self){
[self setParameterEncoding:AFJSONParameterEncoding];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
}
return self;
}
#end
This is how i am making request to the server:
[[APIClient sharedInstance] getPath:#"wines"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error fetching wines!");
NSLog(#"%#",error);
}];
Now i have a class called LoginViewController which gets token number ones i loged in.
Now my question in how to set the token as an Authorization header in my AFHttpClinet class.
and make rest other request using the Authorization header.
Can any one help me out with this?
You can use methods of AFNetworking
setAuthorizationHeaderWithUsername:password:
+(id)sharedInstance{
static APIClient *__sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedInstance = [APIClient alloc]initWithBaseURL:[NSURL URLWithString:BaseURLString]];
[__sharedInstance setAuthorizationHeaderWithUsername:#"username" password:#"password"];
});
return __sharedInstance;
}
OR
+(id)sharedInstance{
static APIClient *__sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedInstance = [APIClient alloc]initWithBaseURL:[NSURL URLWithString:BaseURLString]];
});
return __sharedInstance;
}
- (void)updateAuthorizationHeaderUsername:(NSString *)username Password:(NSString *)password
{
[self setAuthorizationHeaderWithUsername:username password:password];
}
- (void)updateAuthorizationHeader:(NSString *)token
{
[self setDefaultHeader:#"token" value:token];
}
Try to do like this
[[APIClient sharedInstance] getPath:#"wines"
parameters:#{#"Authorization":TOKEN}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error fetching wines!");
NSLog(#"%#",error);
}];
Where TOKEN is you token
I am trying to post a JSON using AFNetworking.
Here's the code that im using:
+ (RESTAPI *)sharedClient
{
static RESTAPI *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:#"https://mybaseurl.com"]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self setParameterEncoding:AFJSONParameterEncoding];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
[self setAllowsInvalidSSLCertificate:YES];
return self;
}
The following code does not works. Everytime i try i get the following error:
The operation couldn’t be completed. (NSURLErrorDomain error -1012.)
// this code does not works
//
- (void)loginNOTWORKING
{
RESTAPI *client = [RESTAPI sharedClient];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
NSDictionary *parameter = #{#"tgout": #"1",
#"tgin": #2,
#"username": #"foo",
#"password":#"bar"};
NSURLRequest *request = [client requestWithMethod:#"POST" path:#"/login" parameters:parameter];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// code for successful return goes here
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
NSLog(#"THIS IS NEVER CALLED: %#", JSON);
// do something with return data
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// code for failed request goes here
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
NSLog(#"SAD, VERY SAD: %#", error.localizedDescription);
// do something on failure
}];
[operation start];
}
This code works:
// this code WORKS
- (void)loginWORKING
{
RESTAPI *client = [RESTAPI sharedClient];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
NSDictionary *parameter = #{#"tgout": #"1",
#"tgin": #2,
#"username": #"foo",
#"password":#"bar"};
[client postPath:#"/login" parameters:parameter success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Print the response body in text
NSLog(#"IT WORKS: %#",responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Response: %#", error.localizedDescription);
}];
}
Why the first login method does not works? What am i doing wrong?
Try by replacing
NSURLRequest *request = [client requestWithMethod:#"POST" path:#"/login" parameters:parameter];
with
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"/*HERE THE URL STRING TO CALL*/"]]
You can find the error -1012 in file CFNetworkErrors.h:
kCFURLErrorUserCancelledAuthentication = -1012
"The connection failed because the user cancelled required authentication."
I guess, there is an issue with your authentication. The error description is possibly misleading with regard to "the user" - it is actually a delegate method that gets invoked which cancels the authentication, or the authentication simply fails.
This of course can be caused by not properly serializing the parameters. I would suggest to use a lower level API, create the request manually, encode the JSON manually with NSJSONSerialization, and set the body data and the URL of the request. IMHO, this is certainly more readable code, and likely requires less code.
I have AFNetworking set up but it is not accept https urls. How can I get AFNEtworking to connect via ssl.
I have the following code:
NSMutableURLRequest *apiRequest =
[self multipartFormRequestWithMethod:#"POST"
path: pathstr
parameters: params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
{
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
NSLog(#"%#", error);
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
operation.securityPolicy.allowInvalidCertificates = YES;
This code is very important. If you dont add this you will get an error.
This will obviously only work if you have a non self-signed cert OR you add:
#define _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ to your pch file. If you are using cocoa pods for this you will likely need to subclass AFHTTPRequestOperation and implement:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
if ([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self bypassSslCertValidation:protectionSpace])
return YES;
else
return [super connection:connection canAuthenticateAgainstProtectionSpace:protectionSpace];
}
return [super connection:connection canAuthenticateAgainstProtectionSpace:protectionSpace];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self bypassSslCertValidation:challenge.protectionSpace]) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
return;
}
else
return [super connection:connection didReceiveAuthenticationChallenge:challenge];
return;
}
}
- (BOOL) bypassSslCertValidation:(NSURLProtectionSpace *) protectionSpace
{
if (ENVIRONMENT_TYPE == DEV_ENV || ENVIRONMENT_TYPE == STAGING_ENV) {
return YES;
}
return NO;
}
Then tell AFNEtworking to use the new subclass:
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#""]];
[client registerHTTPOperationClass:[YourSubClassHTTPRequestOperation class]];
It's not the easiest thing to do in the world and technically ignoring self-signed isn't making it work, but if you use standard SLL certificates It's probable it will work just fine, remember to remove this code or make it only available when debugging if you plan to release.
Adding to answer because comments have char limits!
Few choices looking at the headers
Return operation that can be manually added to the queue:
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
Or pass in your custom subclass operation to this one:
- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation;
Try this code.
NSMutableURLRequest *apiRequest =
[self multipartFormRequestWithMethod:#"POST"
path: pathstr
parameters: params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
{
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
NSLog(#"%#", error);
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
operation.securityPolicy.allowInvalidCertificates = YES;
[operation start];
If AFNetworking is disabled subclass over superclass implementation then simply put in code like :http//foo//bar and set this to bool
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?
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