4

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=/',

marisbest2
  • 1,346
  • 2
  • 17
  • 30
  • Verify what is being sent/received with Charles Proxy, it is indispensable to me for network issues (free 30 day trial). – zaph Jul 24 '14 at 15:06
  • I'm able to view all the requests on both the client and server side. still no luck. I've updated the post with the only difference between the two – marisbest2 Jul 24 '14 at 15:40
  • In Charles you can breakpoint on a message, modify and send it on it's way. You might try and see if the difference is the problem. – zaph Jul 24 '14 at 15:49
  • it doesnt seem to be even registering my requests from the iOS simulator. Do I need to configure that separately? – marisbest2 Jul 24 '14 at 16:00
  • ideas on how to make Charles work? (I've looked at the docs, but I couldnt find anything) – marisbest2 Jul 24 '14 at 16:36

3 Answers3

6

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"];
marisbest2
  • 1,346
  • 2
  • 17
  • 30
1

Take off SessionAuthentication from authentication_classes in the corresponding API view. It will "disable" cookies for this view, which means CSRF-token won't be required anymore.

For instance:

authentication_classes = [SessionAuthentication, BasicAuthentication, JSONWebTokenAuthentication]

becomes

authentication_classes = [BasicAuthentication, JSONWebTokenAuthentication]
0

For the sake of completeness, I'll add my answer:

You must make a petition with header

X-CSRF-Token -> Fetch

(depending on the case, you might need to attach some type of authentication)

And in your response, you will receive the same header with the token, eg:

X-CSRF-Token -> <Value>

It's the method used in some SAP Enterprise services, must be the same for this Django one

webo80
  • 3,365
  • 5
  • 35
  • 52