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:@"[email protected]" 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=/',
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"];
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