1

I have a ROR application with an API secured by Devise + simple_token_authentication - all working fine. Now I'm building an iOS application using NSURLSession to access the API and handle authentication, which is where i get into trouble.

On load i call the following method to retrieve data from the server. As i understand it, the didReceiveChallenge delegate should be called when getting a 401 unauthorized but nothing happens. I am fairly new to iOS and i might be doing it completely wrong, but i hope someone can help me get past this issue. Thanks!:)

- (void)fetch:(id)sender {

    NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    config.HTTPAdditionalHeaders = @{ @"Accept":@"application/json"};

    self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];

    NSURLSessionTask *dataTask = [self.session dataTaskWithURL:[NSURL URLWithString:@"http://localhost:3000/api/v1/tasks"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        // handle response
        NSLog(@"data %@", data);
    }];

    [dataTask resume];

}

This method never gets called, even after receiving a 401 header.

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {

    NSString *user = @"email";
    NSString *password = @"secretpass";

    NSLog(@"didReceiveChallenge");

    // should prompt for a password in a real app but we will hard code this baby
    NSURLCredential *secretHandshake = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistenceForSession];

    // use block
    completionHandler(NSURLSessionAuthChallengeUseCredential,secretHandshake);

}

2 Answers 2

1

If you include a completionHandler in the dataTaskWithURL, then that is what is used and the delegate is never called.

Try setting the completionHandler to Nil:

NSURLSessionTask *dataTask = [self.session dataTaskWithURL:[NSURL URLWithString:@"http://localhost:3000/api/v1/tasks"] completionHandler:Nil];

Then the delegate methods will be used.

On testing this further, you need to return a WWW-Authenticate header to trigger the didReceiveChallenge delegate method. From Apple docs:

Important: The URL loading system classes do not call their delegates to handle request challenges unless the server response contains a WWW-Authenticate header. Other authentication types, such as proxy authentication and TLS trust validation do not require this header.

You can do this by adding a custom authenticate method to your rails app e.g.

  before_filter :authenticate_user!

  private

    def authenticate_user!
      unless current_user
          headers["WWW-Authenticate"] = %(Basic realm="My Realm")          
          render :json => {:message =>I18n.t("errors.messages.authorization_error")}, :status => :unauthorized
      end
    end

I'm still working through this on my app but the above approach does fire the didReceiveChallenge delegate. Hope you find it useful.

A bit more info, the following code in didReceiveChallenge (same as in original question) will handle the logon to the Rails server:

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
 //   NSLog(@"didReceiveChallenge");

    NSString *ftfID    = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:@"ftfID"];
    NSString *ftfPassword    = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:@"ftfPassword"];
    NSString *user = ftfID;
    NSString *password = ftfPassword;

    NSURLCredential *secretHandshake = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistenceForSession];

    // use block
    completionHandler(NSURLSessionAuthChallengeUseCredential,secretHandshake);

}

To download working examples of a Rails and iOS app communicating via json requests see https://github.com/petetodd/bright-green-star-client and https://github.com/petetodd/bright-green-star-server.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks greentor! this is very helpful. didReceiveChallenge is now working. If you find out some more good stuff, please post it:] i will accept your answer soon
Glad you found it useful. If you're looking to Post data using NSURLSession you might find this answer useful: Using NSDictionary to Post params In my iPhone app I register/logon users then post data to my Rails app.
Hey @greentor, can i ask how you are handling the json login process with devise?
A bit more info now added to the answer showing how to deal with logon challenge.
Hi @user3108730 - I think you are already doing what I added for didReceiveChallenge. I attempt a post, a challenge is received, logon successful and the post continues (with Rails app using the User I logged on as). If your didReceiveChallenge fires what response are you getting from devise? (use didCompleteWithError delegate to get details)
|
0

Be sure you're using the :database_authenticatable strategy in your User model and config.http_authenticatable = true in the devise initializer:

# models/user.rb
...
devise :database_authenticatable, # and more
...


# config/initializers/devise.rb
...
config.http_authenticatable = true
...

2 Comments

Hey Troy, thanks for your answer. I didn't have config.http_authenticatable = true, but it still doesn't work. I think the problem might be more related to the iOS part and the didReceiveChallenge delegate
Try adding the other delegate methods to see what's going on - i.e. – URLSession:task:didCompleteWithError: and – URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.