How to add an Auth Token in every request using AFIncrementalStore? - ios

I have an iOS + Rails 3.1 app, and I'm using AFIncrementalStore for the client-server communication.
I have implemented Token Authentication on my Rails server according to this tutorial: http://matteomelani.wordpress.com/2011/10/17/authentication-for-mobile-devices/
I now want to include the &auth_token=XXXXXXXX in every request from client to server, including POST requests. How would I do that? I haven't found the solution in this related post: Using AFIncrementalStore with an Auth token
UPDATE: this is my first code attempt, but doesn't seem to send the auth_token:
(inside my AFIncrementalStoreHTTPClient sub-class)
- (NSMutableURLRequest *)requestForFetchRequest:(NSFetchRequest *)fetchRequest withContext:(NSManagedObjectContext *)context {
NSMutableURLRequest *request = [[super requestForFetchRequest:fetchRequest withContext:context] mutableCopy];
NSMutableString *requestBody = [[NSMutableString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding];
[requestBody appendFormat:#"&%#=%#", #"auth_token", #"xkT2eqqdoNp5y4vQy7xA"];
[request setHTTPBody:[requestBody dataUsingEncoding:NSUTF8StringEncoding]];
return request;
}

UPDATE: I skimmed your question (sorry!), and my sample code below works for a regular AFHTTPClient, but not AFIncrementalStore. The same basic approach will work, though, and there's sample code at this answer that should point you in the right direction.
You can't just append &auth_token=whatever to the end of your HTTP body in all cases.
You probably want to override your getPath... and postPath... methods with something like:
- (void)getPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
if (parameters) {
// Make a mutable copy and add the "token" parameter to the dictionary
NSMutableDictionary *mutableParams = [parameters mutableCopy];
[mutableParams setObject:#"whatever" forKey:#"token"];
parameters = [NSDictionary dictionaryWithDictionary:mutableParams];
} else {
parameters = #{#"token" : #"whatever"};
}
[super getPath:path parameters:parameters success:success failure:failure];
}
This approach will allow AFNetworking to appropriately encode your parameters depending on your specific request and encoding settings.
If you are rolling your own AFHTTPRequestOperation objects instead of using the convenience methods (you probably aren't), just make sure you include the token in parameters before you create your NSURLRequest like so:
NSURLRequest *request = [self requestWithMethod:#"GET" path:path parameters:parameters];

Related

RestKit , Intercept failed request/ retry if it was caused due to token Expiration

I have a class called API helper with a Method that looks like this:
+(RKObjectManager*) getRestObjectManager{
NSURL *baseURL = [NSURL URLWithString:BASE_URL];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
// initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
return objectManager;
}
And I will create classes like API_User , API_Group etc. Each of these classes will have methods like
+(void)getDetails:(void (^)(User* user) )onSuccess{
//fetch object manager from api helper and perform request, on success, call the onSuccess block from the function parameter.
onSuccess(user); //if it was successful, i will create a user object and //return.
}
There will be several methods like getDetails , each which require an authentication token to be sent to work. The token can expire , and needs to be refreshed.
How do I :
Define some sort of an interceptor in API helper , so that when a request fails , it will fetch a new token (my token expired response itself provides a new token) ,and retry the request that had failed? I don't want to handle this for each and every endpoint that I define.
What I did was Extend RKObject Manager and handled failures there like so :
#implementation MYOWNObjectManager
#pragma mark - RKObjectManager Overrides
- (void)getObjectsAtPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(RKObjectRequestOperation *operation,
RKMappingResult *mappingResult))success failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure {
[super getObjectsAtPath:path parameters:parameters success:success failure:^(RKObjectRequestOperation *operation, NSError *error) {
//check if failure was due to token expiry, if yes call the code to refresh token. otherwise just call failure(operation, error);
[super getObjectsAtPath:path parameters:parameters success:success failure:failure]; //this line performs the request again.
}];
}
This snippet is for GET only. You will also need to override PUT/POST etc with the same logic

iOS: Accessing wsdl service api from iOS client

I have a web service created through Eclipse. Created a web service method and wsdl for that class. I would like to know how can i access this web service api from my iOS client? For ex: my wsdl file is 'ReceiverClass.wsdl' and 'ReceiverClass.java class contains a method called 'RespondResult(..)'. I know about NSURLConnection, i'm asking how can i point to this url api?
Thank you!
Getsy.
You'll need a SOAP framework. There's not one built into iOS, but here's one which was the first hit on Github: https://github.com/priore/SOAPEngine.
You can use this iPhone Web Services Client. Also can refer to these tutorials.
SOAP Based Web Services Made Easy On The iOS Platform
Working with iOS and SOAP
There is no easy solution, easiest one is to move away from SOAP.
Meanwhile you can try http://sudzc.com/ or you can write a simple wrapper yourself which will send and receive XML packets.
In example below, I observed the xml packets through "Poster" firefox plugin and made generic methods to make SOAP request. The biggest drawback is that I'm ignoring WSDL and I have to implement each method myself (unlike Java).
- (void) requestData:(NSString *) request withParameters:(NSDictionary*)parameters
soapAction:(NSString*)soapAction serviceName:(NSString *)url
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
NSString *packet = [self formatRequest:request WithParameters:parameters];
NSData *envelope = [packet dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request1 = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
[request1 setHTTPMethod:#"POST"];
[request1 setHTTPBody:envelope];
[request1 setValue:#"text/xml" forHTTPHeaderField:#"Content-Type"];
AFHTTPRequestOperation * operation = [[AFHTTPRequestOperationManager manager] HTTPRequestOperationWithRequest:request1 success:success failure:failure];
operation.responseSerializer = [AFHTTPResponseSerializer serializer];
[[NSOperationQueue mainQueue] addOperation:operation];
}
- (NSString*) formatRequest:(NSString*)request WithParameters:(NSDictionary *)parameters
{
NSMutableString *packet = [[NSMutableString alloc] init];
[packet appendString:#"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">"
"<soap12:Body>"];
[packet appendFormat:#"<%# xmlns=\"http://tempuri.org/\">",request];
for (NSString *key in parameters) {
[packet appendFormat:#"<%#>%#</%#>",key,[parameters objectForKey:key],key];
}
[packet appendFormat:#"</%#>",request];
[packet appendString:#"</soap12:Body></soap12:Envelope>"];
return packet;
}

Upgrading to AFNetworking

I am taking over an old iOS project from developers no longer part of the project - the app is getting a rewrite and with that I am going to support iOS7 and upwards only.
So, I wanted to use AFNetworking 2.0 instead of ASIHTTPRequest - the reason behind this is NSURLSeesion. AFNetworking 2.0 supports NSURLSession and with that I can get my app to download content in the background at opportunistic times (According to Apple - NSURLSession must be used and Background Fetch mode turned on, for this to work? )
Let me start out by saying I am a new developer to iOS and networking stuff goes a little over my head - but I am determined to learn more about it and as much as I can. I have read AFNetworking documentation as well, but I fear since some of the terminology escapes me (Request, Response, Sterilisation, etc) - I am not grasping them 100%.
So, I took a look at the ASIHTTPRequest code the previous developer used to, from what I can see, build a GET / POST request - This is the code they used:
+ (ASIHTTPRequest*) buildRequest: (NSString*) url RequestType: (NSString*) requestType
PostData: (NSString*) postData
Host: (NSString*) host
ContentType: (NSString*) contentType
SoapAction: (NSString*) soapAction
RequestProperties: (NSDictionary*) requestProperties
{
NSURL *url = [NSURL URLWithString: url];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:u] autorelease];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[request setTimeOutSeconds:20];
[request setQueuePriority:NSOperationQueuePriorityVeryHigh];
if (host != nil)
[request addRequestHeader: #"Host" value: host];
if (contentType != nil)
[request addRequestHeader: #"Content-Type" value: contentType];
if (soapAction != nil)
[request addRequestHeader: #"SOAPAction" value:soapAction];
if (requestType != nil)
[request setRequestMethod: requestType];
if (postData != nil)
{
NSMutableData* mPostData = [NSMutableData dataWithData:[postData dataUsingEncoding:NSUTF8StringEncoding]];
NSString *msgLength = [NSString stringWithFormat:#"%d", [postData length]];
[request setPostBody: mPostData];
[request addRequestHeader: #"Content-Length" value:msgLength];
}
if (requestProperties != nil)
{
for (int i = 0; i < [[requestProperties allKeys] count]; i++)
{
[request addRequestHeader:[[requestProperties allKeys] objectAtIndex: i] value: [requestProperties objectForKey:[[requestProperties allKeys] objectAtIndex: i]]];
}
}
return request;
}
I'm trying to understand this code and upgrade it to use AFNetworking V2.0 instead. I assume, just replacing ASIHTTPRequest with AFHTTPRequestOperation will not do the trick, correct?
I have been given some help and also managed to do a lot of digging around to see how I can get this right.
I made the method simpler - as I did not need Soap / Content-type, etc - just urlParamerters and some basic stuff:
This is the answer I came up with:
+ (AFHTTPSessionManager *) buildRequest: (NSString*) url RequestType: (NSString*) requestType PostDataValuesAndKeys: (NSDictionary*) postData RequestProperties: (NSDictionary*) requestProperties
{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
if ([requestType isEqualToString:#"GET"])
{
[manager GET:url parameters:postData success:^(NSURLSessionDataTask *dataTask, id responseObject){
//Success
NSLog (#"Success");
NSData *xmlData = responseObject;
NSLog(#"Got XML Data: %#", xmlData);
}
failure:^(NSURLSessionDataTask *dataTask, NSError *error){
//Failure
NSLog (#"Failure");
}];
}else if ([requestType isEqualToString:#"GT"]){
[manager POST:url parameters:postData success:^(NSURLSessionDataTask *dataTask, id responseObject){
//Success
}
failure:^(NSURLSessionDataTask *dataTask, NSError *error){
//Failure
NSLog (#"Failure");
}];
}
return manager;
}
It will work for what I need it to do - but I am not sure if it's the best way to do it.
I couldn't see how I could detect the requestType other thank with looking at the NSString value. I looked into the AFHTTPSessionManager.h file for some clues on what to do with that - Matt suggests overriding the GET / POST methods if I want them done differently - per his comments in the header file:
Methods to Override
To change the behavior of all data task operation construction, which
is also used in the GET / POST / et al. convenience methods,
override dataTaskWithRequest:completionHandler:.
Also there is a requestSerializer property in that file - which you could use to detect the type of request - however it's implementation goes to the super class: AFURLSessionManager
In that class - there is a requestWithMethodmethod.
So, I tried to do this instead:
If I try implement that method - then I am not using the convince methods in AFHTTPSessionManager:
(NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
Unless I have that completely wrong. After that I decided to just check the requestType using [NSString isEqualToString]

OAuth 2 bearer Authorization header

With an update to the client's API the HTTPBasicAuthication method has been replace with a OAuth2 Bearer Authorization header.
With the old API I would do the following:
NSURLCredential *credential = [NSURLCredential credentialWithUser:self.account.username
password:self.account.token
persistence:NSURLCredentialPersistenceForSession];
NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:kAPIHost
port:443
protocol:NSURLProtectionSpaceHTTPS
realm:#"my-api"
authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
But this will not work with the Bearer header.
Now normally I would just add the header my self by adding it like so:
NSString *authorization = [NSString stringWithFormat:#"Bearer %#",self.account.token];
[urlRequest setValue:authorization forHTTPHeaderField:#"Authorization"];
But the problem with this solutions is that the API redirect most of the calls to other URLs, this has to do with security.
After the NSURLRequest gets redirected the Authorization header is removed from the request and since I'm unable to add the Bearer method to the NSURLCredentialStorage it can't authenticate any more after being redirected.
What would be a good solutions? I can only think to catch the redirect and modify the NSURLRequest so it does include the Bearer header. But how?
Well after much research I found out that I will just have to replace the NSURLRequest when a call is redirected.
Not as nice as I would like it to be, but is does work.
I used AFNetworking and added the redirect block, then check wether the Authorization header is still set if not I create a new NSMutableURLRequest and set all the properties to match the old request (I know I could have just created a mutable copy):
[requestOperation setRedirectResponseBlock:^NSURLRequest *(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse) {
if ([request.allHTTPHeaderFields objectForKey:#"Authorization"] != nil) {
return request;
}
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:request.URL cachePolicy:request.cachePolicy timeoutInterval:request.timeoutInterval];
NSString *authValue = [NSString stringWithFormat:#"Bearer %#", self.account.token];
[urlRequest setValue:authValue forHTTPHeaderField:#"Authorization"];
return urlRequest;
}];
I'm using AFNetworking Library
Find AFHttpClient.m and you have a method
- (void)setAuthorizationHeaderWithToken:(NSString *)token {
[self setDefaultHeader:#"Authorization" value:[NSString stringWithFormat:#"Token token=\"%#\"", token]];
}
replace this method with the following or if you need it for back compatibility keep it an add with a different name and use that name
- (void)setAuthorizationHeaderWithToken:(NSString *)token {
[self setDefaultHeader:#"Authorization" value:[NSString stringWithFormat:#"Bearer %#", token]];
}
then make the request with oauth access token. (Following is a GET method service)
NSURL *url = [EFServiceUrlProvider getServiceUrlForMethod:methodName];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[httpClient setAuthorizationHeaderWithToken:#"add your access token here"];
[httpClient getPath:#"" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *response = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//
}];
Updated
Use Oauth2 Client on AFNetworking written by matt
https://github.com/AFNetworking/AFOAuth2Client
If you happen to be having this issue with Django rest framework and the routers the problem might be related to the trailing slash being clipped by the NSUrlRequest. if the trailing slash is clipped then django will have to redirect your request, to avoid this you can use Trailing_slash = True like this
router = routers.DefaultRouter(trailing_slash=False)
That way not your authorization header nor your parameters will get lost.
Hope this saves somebody some time.

AFNetworking: Append parameters as the query string for PUT request

First of all, I realize that for a PUT request the request parameters should be passed in the request body. However, I am working with an API (which I am only consuming, not developing) that expects the request parameters to be appended as the query string for a PUT request.
I am making use of a subclass of AFHTTPClient. For the particular PUT request that I am referring to, I make use of getPath:parameters:success:failure:.
The solution that I have found so far is manually constructing the path variable to include the parameters I want to pass. Of course, this is not ideal and I was wondering if there is another option that is less error prone.
In short, is there a way to send a PUT request using AFHTTPClient (a subclass of) with the passed parameters appended (and encoded) as the query string (just like a GET request)?
The getPath:parameters:success:failure method inside AFHTTPClient.m calls requestWithMethod:path:parameters. Inside the latter method, the HTTP method is checked against certain values to decide how to append the parameters to the request. As you can see, by default, the parameters should only be appended to the URL in case of a GET, HEAD or DELETE request. Since you need them to be appended to the URL in case of a PUT request too, modify the requestWithMethod:path:parameters like this:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
path:(NSString *)path
parameters:(NSDictionary *)parameters
{
NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:url] autorelease];
[request setHTTPMethod:method];
[request setAllHTTPHeaderFields:self.defaultHeaders];
if ([method isEqualToString:#"GET"] || [method isEqualToString:#"HEAD"]) {
[request setHTTPShouldUsePipelining:YES];
}
if (parameters) {
if ([method isEqualToString:#"GET"] || [method isEqualToString:#"HEAD"] || [method isEqualToString:#"DELETE"] || [method isEqualToString:#"PUT"]) {
url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:#"?"].location == NSNotFound ? #"?%#" : #"&%#", AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding)]];
[request setURL:url];
} else {
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding));
switch (self.parameterEncoding) {
case AFFormURLParameterEncoding:;
[request setValue:[NSString stringWithFormat:#"application/x-www-form-urlencoded; charset=%#", charset] forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding) dataUsingEncoding:self.stringEncoding]];
break;
case AFJSONParameterEncoding:;
[request setValue:[NSString stringWithFormat:#"application/json; charset=%#", charset] forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[AFJSONStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]];
break;
case AFPropertyListParameterEncoding:;
[request setValue:[NSString stringWithFormat:#"application/x-plist; charset=%#", charset] forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[AFPropertyListStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]];
break;
}
}
}
return request;
}
You can just do what the code in datwalk's answer is doing without modifying the underlying AFNetworking code. Use AFNetworking to create a path that includes URL parameters:
NSDictionary *mutableParameters = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"csv",#"format", #"0",#"level", #"2013-10-25", #"keydate", nil];
NSString *urlPath = [NSString stringWithFormat:#"applications/%#/Planning?%#", name, AFQueryStringFromParametersWithEncoding(mutableParameters, NSUTF8StringEncoding)];
NSString *apiFunctionPath = [urlPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[self putPath:apiFunctionPath
parameters:nil
success:^(AFHTTPRequestOperation *operation, id XML) {
NSLog(#"%#",XML);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#",error);
}];
For the case of my API, changing the encoding of the AFHTTPClient object solved the issue:
myAFHTTPClient.parameterEncoding = AFJSONParameterEncoding;

Resources