Reputation: 33
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);
}
Upvotes: 1
Views: 1839
Reputation: 8651
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.
Upvotes: 1
Reputation: 5399
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
...
Upvotes: 0