Is it possible to use Basic and OAuth authorization headers in the same request with AFNetworking (avoiding the overwriting) ?
I have this code:
NSURL *url = [NSURL URLWithString:#"https://www.infojobs.net/"];
AFOAuth2Client *OAuthClient = [[AFOAuth2Client alloc] initWithBaseURL:url clientID:kClientID secret:kClientSecret];
[OAuthClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[OAuthClient authenticateUsingOAuthWithPath:#"oauth/authorize" code:self.authorizationCode redirectURI:kInfoJobsRedirectURLString success:^(AFOAuthCredential *credential) {
NSLog(#"Credentials: %#", credential.accessToken);
if (![credential.accessToken isEqualToString:#""]) {
self.isAuthenticated = YES;
[AFOAuthCredential storeCredential:credential withIdentifier:#"kInfoJobsAccessToken"];
[[InfoJobsAPI sharedClient] setAuthorizationHeaderWithToken:credential.accessToken];
// (!) This overwrites the Authorization header set with the accessToken
[[InfoJobsAPI sharedClient] setAuthorizationHeaderWithUsername:kClientID password:kClientSecret];
success(credential);
}
} failure:^(NSError *error) {
NSLog(#"Error: %#", error.localizedDescription);
}];
And I need a request like this:
GET /api/1/application HTTP/1.1
Host: api.infojobs.net
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Authorization: OAuth 07d18fac-77ea-461f-9bfe-a5e9d98deb3d
....
But I can't set the "Basic" and "OAuth" Authorization headers in the same request because AFNetworking seems to overwrite this header as seen in documentation
It's possible to use "Basic" and "OAuth" in a same Authorization header, maybe splitting both with a "\n" ?
Thanks, and sorry for my poor english
Edit
Finally, I can use the "Basic" and "Oauth" authentications in the same header, this is the code:
[[InfoJobsAPI sharedClient] setAuthorizationHeaderWithUsername:kClientID password:kClientSecret];
AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:#"kInfoJobsAccessToken"];
NSMutableURLRequest *request = [self requestWithMethod:#"GET" path:#"/api/2/candidate" parameters:nil];
[request addValue:[NSString stringWithFormat:#"OAuth %#", credential.accessToken] forHTTPHeaderField:#"Authorization"];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
DLog(#"Response : %#",JSON);
}failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
DLog(#"Error : %#",error);
}];
[operation start];
According to the HTTP specification there can be only one Authorization header in a request. So the behavior the library is showing is correct according to that specification: the second call to setAuthorizationHeader... overwrites the previous one.
What you'll typically see in HTTP is that there is a handshaking phase, where the server tells the client what authorization protocols it can accept. The client can then choose from those protocols, which one it wants to use.
Related
I need to upload video files from my app to a server. I tried doing so via [AFHTTPRequestOperationManager post:parameters:success:failure] but unfortunately kept getting request timeouts. I'm now trying something similar to Creating an Upload Task from the AF Docs.
I read on SO and the AF Docs about setSessionDidReceiveAuthenticationChallengeBlock: and tried to implement the whole upload malarky as follows:
__block ApiManager *myself = self;
// Construct the URL
NSString *strUrl = [NSString stringWithFormat:#"%#/%#", defaultUrl, [self getPathForEndpoint:endpoint]];
NSURL *URL = [NSURL URLWithString:strUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"POST"];
// Build a session manager
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// Set authentication handler
[manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *credential) {
*credential = myself.credentials;
return NSURLSessionAuthChallengeUseCredential;
}];
// Create the upload task
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
[myself endpoint:endpoint returnedFailure:error];
} else {
[myself endpoint:endpoint returnedSuccess:responseObject];
}
}];
// and run with it
[uploadTask resume];
The myself.credentials object has been set previously to have the correct username and password. Whenever this request fires, I get 401 unauthorised as a response. I tried putting NSLog(#"CHALLENGE") inside the challenge block above, but it never seems to get called, so AFNetworking isn't giving me a way to supply credentials. I know that this works perfectly well on the server side because I've tested it with Postman.
How can I get AFNetworking to let me supply credentials for HTTP Basic Auth with this upload task?
I'm not sure about AFNetworking's setSessionDidReceiveAuthenticationChallengeBlock, but as long as you have an NSMutableURLRequest you may set the Authorization HTTP Header directly on the request object:
[request setValue:base64AuthorizationString forHTTPHeaderField:#"Authorization"];
Keep in mind that the value must be base64 representation of the username:password string.
Otherwise, for GET, POST, etc. requests on a session manager, you may set the credentials on the request serializer used by the session manager. See:
[AFHTTPRequestSerializer setAuthorizationHeaderFieldWithUsername:password:]
[AFHTTPRequestSerializer clearAuthorizationHeader]
I am using AFNetworking in my project and I simply need to check if a URL is successful (status 2xx).
I tried
NSURLRequest *request = [NSURLRequest requestWithURL:url2check];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
if (response.status == 200) {
NSLog(#"Ok %#", JSON);
}
}
failure:nil
];
[operation start];
But as I'm not awaiting JSON especially, I was expecting to use something like
AFHTTPRequestOperation *operation = [AFHTTPRequestOperation
HTTPRequestOperationWithRequest: success: failure:];
But this does not exist (I don't know why), so I used
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCompletionBlockWithSuccess:...]
Isn't there simpler ?
How would you do this simple URL check with AFNetworking ?
Here is what I ended up with
NSURLRequest *request = [NSURLRequest requestWithURL:url2check];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation
, id responseObject) {
// code
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// code
}
];
[operation start];
Feel free to add a simpler way.
You just could instantiate a AFHTTPClient object with the base URL and call getPath:parameters:success:failure: on it.
from the docs:
Creates an AFHTTPRequestOperation with a GET request, and enqueues it to the HTTP client’s operation queue.
I am trying to do a request on a server, which responds with an Etag for cache purposes. I have written the following code for it, but the response for these calls is random most of the time i.e. sometimes response status code is 200 and some times 304(expected). Am I doing something wrong in my code or is there something specific with AFNetworking that I should keep in mind.!
NSURL *url = [NSURL URLWithString:#"http://ia.media-imdb.com/images/M/MV5BNzI0NTY5NzQwMV5BMl5BanBnXkFtZTcwOTQyNTA5OA##._V1._SY90_.jpg"];
NSMutableURLRequest *aRequest = [NSMutableURLRequest requestWithURL:url];
[aRequest setValue:#"\"61-smtLpBSL_SY90_#1\"" forHTTPHeaderField:#"If-None-Match"];
NSLog(#"headers: %#", aRequest.allHTTPHeaderFields);
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:aRequest imageProcessingBlock:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(#"%# %d", response.allHeaderFields, response.statusCode);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Request failed with error: %#", error);
}];
[operation start];
I had the same problem, I managed to fix this by changing my request to
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:60];
I have tried your code and I receive status 200 - OK which is like it should be.
Code 304 - Not modified, I wasn't able to get. I have tried running command several times fast and slow but I always get 200 as a response.
Do you have more details when exactly it happens that you get 304 as response code?
304 response code generally means that the content hasn't been modified and you should get original request that had 200 as a response included in it.
Here you can read a bit more about Etag: ETag vs Header Expires
I also had a problem with the Etag logic and AFNetworking. My problem was just the cachePolicy not correctly set...
var manager = AFHTTPRequestOperationManager()
manager.requestSerializer = AFHTTPRequestSerializer()
manager.requestSerializer.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
manager.requestSerializer.setValue(etag, forHTTPHeaderField: "If-None-Match")
manager.GET(fullPath, parameters: parameters, success: {}, failure: {}))
I hope it will help someone !
I'm having an odd issue here, and surprised I haven't found anyone else with the same problem.
I'm using AFNetworking to make a AFJSONRequestOperation.
It works the first time a network connection is made. However, the same code fails once a network connection is made and displays a 'Bad URL' error.
The weird part is, the app never even pings the server before failing, I'm using Charles to sniff all requests.
Has anyone else experienced this?
For reference, here is the code:
NSURL *url = [NSURL URLWithString:JOIN_URL];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
// httpClient.parameterEncoding = AFJSONParameterEncoding;
NSString *path = [NSString stringWithFormat:#"%#?%#",JOIN_URL, getString];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:path parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"SUCCESS JSON: %#", JSON);
NSLog(#"RESPONSE URL: %#",response.URL);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"FAIL JSON: %#", JSON);
NSLog(#"FAIL ERROR: %#", error.description);
NSLog(#"RESPONSE URL: %#",response.URL);
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Connection Error" message:#"Cannot connect now, please try again" delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles: nil];
[alert show];
}];
[operation start];
As far as I understand AFHTTPClient, you provide it with a baseURL which is the Base URL to which all path you specify will be appended. And then when you provide a path, you only provide the relative part of this path.
So if you have a WebService at http://www.example.com/webservice/ which has some methods like /listAll?n=10 for example, you will only provide "listAll" to the path argument of requestWithMethod:path:parameters: and a dictionary #{ #"n" : #10 } to the parameters argument.
You already provided your JOIN_URL when you instanciated your AFHTTPClient anyway, so if you pass that JOIN_URL again in the path, it will appear twice in the URL built by the AFHTTPClient internally!
You should really be encoding your URL path string like so:
NSString* escapedUrlString =[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
Objective-C:
[query stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]];
Swift:
query.stringByAddingPercentEncodingWithAllowedCharacters(
NSCharacterSet.URLQueryAllowedCharacterSet())
In looking at the AFNetworking documentation, the Put and Delete methods take in a path and a dictionary of parameters. I am using Rails as my backend which expects these two types to take the form of Put /object/1.json and Delete /object/1.json. Should I build up a path string by adding in the Id or do I send a Put or Delete with the Id as one of the params in the Dictionary?
Typically when I do with PUT and similar type HTTP requests when using AFNetworking is something like this:
// Create an HTTP client with your site's base url
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://example.com"]];
// Setup the other parameters you need to pass
NSDictionary *parameters = #{#"username" : #"bob", #"password" : #"123456"};
// Create a NSURLRequest using the HTTP client and the path with your parameters
NSURLRequest *request = [client requestWithMethod:#"PUT" path:#"object/1.json" parameters:parameters];
// Create an operation to receive any response from the server
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// Do stuff
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// Handle error
}];
// Begin the operation
[operation start];
If you look in AFHTTPClient.h there are examples for how to format your base url and your paths. There's more information on these methods in the AFNetworking documentation