marisbest2 marisbest2 - 5 months ago 166
iOS Question

POST with CSRF token to Django Server from iOS

I have an iOS app that needs to login to an existing site that uses Django and requires a CSRF token to login. I cannot change that.

My current attempt was to send a GET to the server which would return a CSRF, and then grab that cookie as a string and append it to the POST request.

NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"http://example.com"]];
[req setHTTPShouldHandleCookies:YES];
[NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
self.csrf_cookie = [[(NSHTTPURLResponse *)response allHeaderFields] valueForKey:@"Set-Cookie"];
[self postLoginCredentialsEmail:@"user@example.com" password:@"password"];
}];

- (void)postLoginCredentialsEmail:(NSString *)email password:(NSString *)password {
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"http://example.com/login"]];
[req addValue:(self.csrf_cookie != nil ? self.csrf_cookie : @"poo") forHTTPHeaderField:@"X-CSRFToken"];
[req setHTTPMethod:@"POST"];
NSString *postData = [NSString stringWithFormat:@"password=%@&;email=%@", password, email];
[req setValue:@"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:@"Content-type"];
[req setHTTPBody:[postData dataUsingEncoding:NSUTF8StringEncoding]];
[NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"response from login: %@", response);
}];

}


I am getting the cookie, but I still get an "incorrect or missing CSRF token" error.

I've seen other posts here, and that's where most of this code comes from but its still failing.

I've compared what is received by the server on my request vs what is received on a good request from the website, and the only difference seems to be that the good request has an HTTP header field
HTTP_X_CSRFTOKEN
with just the CSRF token whereas my request has the format
'HTTP_X_CSRFTOKEN': 'csrftoken=tokenkey; expires=expirydate; Max-Age=age; Path=/, mws-track-id=somestuff; httponly; Path=/',

Answer

Well I managed to solve the issue by saving the cookies from the request using NSHTTPCookieStorage then iterating through those and when cookie.name was @"csrftoken" I took cookie.value and assigned it to a static variable csrf_cookie. I then attached that to the NSMutableURLRequest for the header field X_CSRFTOKEN while also attaching the full NSHTTPCookieStorage

Here's some code:

// at the top 
static NSString *csrf_cookie;

// in a function:
NSURL *url = [[NSURL alloc] initWithString:@"http://example.com"];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

[request setHTTPShouldHandleCookies:YES];

[request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url]]];

// make GET request are store the csrf
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                           NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse *)response allHeaderFields] forURL:url];
                           [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:url mainDocumentURL:nil];
                           // for some reason we need to re-store the CSRF token as X_CSRFTOKEN
                           for (NSHTTPCookie *cookie in cookies) {
                               if ([cookie.name isEqualToString:@"csrftoken"]) {
                                   csrf_cookie = cookie.value;
                                   break;
                               }
                           }

// then in later requests:

NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];

[req setHTTPMethod:@"POST"];

[req setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url]]];

[req addValue:csrf_cookie forHTTPHeaderField:@"X_CSRFTOKEN"];