How to change credentials for NSURLSession

928 views Asked by At

I have the following problem: I am making requests to a server, which uses HTTP basic auth and gives me

  • a 401 if I do not send authentication
  • a 401 if I send invalid authentication
  • a 403 if I send valid authentication but the resource is forbidden for that user.

When receiving the 401s, my NSURLSession is friendly enough to call out to its delegate via

URLSession(
    session: NSURLSession,
    task: NSURLSessionTask, didReceiveChallenge
    challenge: NSURLAuthenticationChallenge,
    completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)

where I can then modify the credentials being sent to the server.

When receiving a 403, however, I run into a problem. From there I cannot get the session to use any other credentials than what it used to get the 403. On a 403, the delegate is not asked (which is ok, it is not a 401), so how do I control which credentials are sent in the future for that protection space? Even manually emptying the shared NSURLCredentialStorage and/or setting the correct credentials there directly does not help:

let credential = NSURLCredential(user: username, password: password, persistence: .ForSession)
    let protectionSpace = NSURLProtectionSpace(
        host: self.host,
        port: self.apiRootURL.port!.integerValue,
        protocol: self.apiRootURL.scheme!,
        realm: "MyRealm",
        authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
    NSURLCredentialStorage.sharedCredentialStorage().setDefaultCredential(credential, forProtectionSpace: protectionSpace)

After this call, the correct credentials are the only ones listed in the NSURLCredentialStorage - but the next call to the server will still include the old credentials, which will inevitably lead to another 403.

Even resetting the session using its reset(completionHandler: @escaping () -> Void) method does not help.

TLDR; how do I change the credentials that NSURLSession continues to use once it has had success with them for a given protection domain?

1

There are 1 answers

4
dgatwood On

You catch the 403 response message, then either

  • Issue a request for a logout URL, then a login URL.
  • Always prevent the credential from being stored in the credential storage so that you'll always get a callback.
  • Remove the existing credential after a 403 to force a callback.

I recommend the second or third approach, depending on which results in fewer requests on average. To avoid persistence, pass NSURLCredentialPersistenceNone or the Swift equivalent instead of telling it to persist for the duration of the session. To remove a credential, call removeCredential:forProtectionSpace:options:.

There are a couple of other possibilities that might work, depending on your app design and the server design:

  • Pre-add the right credential into the keychain/credential store. Caveat: there's only one keychain backing it, so this isn't a great approach if you have multiple simultaneous requests that need to come from different accounts, because you won't have much control over which credential gets sent for a given request.
  • Use a different authentication realm for things that require authentication as a different user.