5

I'm using Adapter-based authentication in native iOS applications to connect my native ios application (swift) to the Mobilefirst server (7.0).

The mechanism of authentication is working fine but the problem comes when the session expires after 10 minutes.

Here you can see the part of the code where I handle the authentication and the session timeout:

override func isCustomResponse(response: WLResponse!) -> Bool {
    if response != nil && response.responseJSON != nil {
        let responseJson: NSDictionary = response.responseJSON as NSDictionary
        if responseJson.objectForKey("authRequired") != nil{
            return responseJson.objectForKey("authRequired") as! Bool
        }
    }
    return false
}

override func handleChallenge(response: WLResponse!) {

    NSLog("A login form should appear")

    if self.vc.navigationController?.visibleViewController!.isKindOfClass(LoginViewController) == true {
        NSLog("Already the login form")
        dispatch_async(dispatch_get_main_queue()) {
            let loginController : LoginViewController! = self.vc.navigationController?.visibleViewController as? LoginViewController

            let myInvocationData = WLProcedureInvocationData(adapterName: "AuthenticationJavaAdapter", procedureName: "authenticate")
            myInvocationData.parameters = [loginController.userID, loginController.userPass]
            self.submitAdapterAuthentication(myInvocationData, options: nil)
        }
    } else if (self.vc.navigationController?.visibleViewController!.isKindOfClass(SignUpViewController) == true) {
        NSLog("Already the signup form")
        dispatch_async(dispatch_get_main_queue()) {
            NSLog("AuthenticationJavaAdapter")
            let sigupController : SignUpViewController! = self.vc.navigationController?.visibleViewController as? SignUpViewController

            let myInvocationData = WLProcedureInvocationData(adapterName: "AuthenticationJavaAdapter", procedureName: "authenticate")
            myInvocationData.parameters = [sigupController.userID, sigupController.userPass]
            self.submitAdapterAuthentication(myInvocationData, options: nil)
        }
    }else { //TEST
        NSLog("A login form is not there yet")
        //After 10 minutes this will execute, it will perform a unwind segue to the login
        //timeOutController is a global var declared in LoginViewController
        timeOutController.performSegueWithIdentifier("logOutDueToTimeOut", sender: nil)
    }
}

When the session expires being the application in background and then comes back to foreground and calls a protected adapter, this part of code is executed:

timeOutController.performSegueWithIdentifier("logOutDueToTimeOut", sender: nil)

The login view loads with success and I can submit again the credentials to login in. The problem is that my application is not longer able to authenticate to the Mobilefirst server, getting this error:

[DEBUG] [WL_REQUEST] -[WLRequest requestFinished:] in WLRequest.m:385 :: no token present
2016-05-13 12:58:29.241 BNNDesignCollection[46327:318014] [DEBUG] [WL_PUSH] -[WLPush updateToken:] in WLPush.m:410 :: Server token is (null)
....
....
....
2016-05-13 12:58:29.352 BNNDesignCollection[46327:318014] [DEBUG] [WL_AFHTTPCLIENTWRAPPER_PACKAGE] -[WLAFHTTPClientWrapper requestFailed:error:] in WLAFHTTPClientWrapper.m:335 :: Response Status Code : 403
2016-05-13 12:58:29.352 BNNDesignCollection[46327:318014] [DEBUG] [WL_AFHTTPCLIENTWRAPPER_PACKAGE] -[WLAFHTTPClientWrapper requestFailed:error:] in WLAFHTTPClientWrapper.m:336 :: Response Error : Expected status code in (200-299), got 403

It seems that the request does not have a token or is invalid, but I don't get the "authrequired" field in the JSON response so that i can't authenticate again as I did the first time the application authenticates before any Mobilefirst session timeout.

The detailed step by step execution is this one:

  1. The application shows the login screen to the user
  2. The user types the credentials
  3. As a protected adapter is called during this process the mobilefirst server returns a response with "authrequired = true". The isCustomResponse method is automatically called, and returns true.
  4. As isCustomResponse returns true, The handleChallenge method is called and as the visible viewController is the loginViewController, the first "If" statement is executed, launching the authentication.
  5. The authentication succeeds and now the user can navigate throughout the application accessing all their protected resources.
  6. I put the application in background for 10 minutes (Established MobileFirst session timeout in the server).
  7. I put the application in foreground and start navigating again.
  8. As the MobileFirst session has expired, once i try to call a protected adapter again the mobilefirst server returns a response with "authrequired = true". The isCustomResponse method is automatically called again, and returns true.
  9. As isCustomResponse returns true, The handleChallenge method is called and as the visible viewController is NOT the loginViewController, the third "If" statement is executed, showing up the login screen again.
  10. The user types the credentials.
  11. The server returns a 403 response. The isCustomResponse method is automatically called but returns false as the response does not contain the "authrequired" field.

Any ideas on how to handle this?

  • How do you know (in code) that the session expired? You wrote that you perform a segue when the session expired, but I don't see anywhere in your code the part that detects a timeout. – Nathan H May 16 '16 at 07:05
  • The isCustomResponse method of the challenge handler is invoked each time that a response is received from the server. If isCustomResponse returns true, the framework calls the handleChallenge method and this is happening when an "authRequired" field is detected in the response. When the session expires and a protected adapter is called, isCustomResponse return true and handleChallenge method is executed. This detection is working fine and the login screen loads again.But, when login again, the worklight server does not return again the "authrequired" field to execute the authentication again. – Irene Marquet May 17 '16 at 10:01
  • Sorry I still don't understand. "But, when login again" - what do you mean by login again? If `isCustomResponse` returned true, it means that you did receive `authrequired` as expected. Please update your question with VERY DETAILED step by step, including code snippets. – Nathan H May 17 '16 at 11:21
  • Hi @NathanH! I've updated the question with the detailed step by step at the end. I hope that it helps better know the explanation – Irene Marquet May 17 '16 at 14:46

1 Answers1

2

There are several problems that I can see with your authentication flow.

  • Your steps starts with "The application shows the login screen to the user". Are you hardcoding the application to start with a login screen? Adapter-based authentication does not support "preemptive" authentication. Meaning only a challenge should show the login screen. The usual way to do this is to call a protected resource during the app startup, or use the login API to trigger the challenge. Your handleChallenge should then display the login screen.
  • "As a protected adapter is called during this process". You are calling a protected resource in the middle of the authentication flow. No good. Don't do that.
  • Your handleChallenge automatically submits the credentials if the LoginViewController view controller is already on screen. What if the user enters incorrect credentials? Wouldn't it send the bad credentials in a loop? Usually, if LoginViewController is already on-screen, you just want to update the UI, for example "wrong credentials, try again".
  • Clicking on the "login" button should be what triggers submitAdapterAuthentication. A challenge should not trigger submitAdapterAuthentication.
  • Every single incoming challenge needs to be answered before you do anything else. Meaning, as soon as handleChallenge is called, you cannot attempt to make any other protected requests or trigger some other challenge.

Regarding your specific issue, this is what I think happens after the timeout:

  • handleChallenge is called, which opens the login screen.
  • You wrote that "a protected adapter is called during this process", which you cannot do because you are in the middle of a challenge.
  • The application enters an unstable state.

If you follow the comments I wrote above, you should be OK. But it will require some architectural changes to your application. The order in which events happen is important:

  1. Make a protected request.
  2. handleChallenge shows the login form.
  3. User clicks on submit.
  4. submitAdapterAuthentication

It does not matter if it's the first run, pre-emptive or after time out, make sure you follow the order of operations.

Nathan H
  • 48,033
  • 60
  • 165
  • 247
  • Thanksss very much for the solution! It is really complete, however while thinking how to implement it I have to add that our application needs to authenticate against a firewall before accessing the MobileFirst server, therefore the challenge handler will not be triggered never before authenticating though this firewall as the Mobilefirst server is just accessible once the application has been authenticated against the firewall. That is why we load the Login screen before. You know how could we deal with this? – Irene Marquet May 31 '16 at 06:58
  • How does the authentication work with your firewall? What kind of authentication? – Nathan H May 31 '16 at 10:16
  • It is using basic access authentication, so we send the username and password in a header – Irene Marquet May 31 '16 at 11:00
  • So, your application uses BOTH adapter-based auth AND Basic Auth? Is the customer expected to enter 2 usernames and 2 passwords? – Nathan H May 31 '16 at 11:40
  • The user enters the same username and password for both authentications, the firewall has Basic Auth and we can't control it. And then we use Adapter-based authentication for authentication in the MobileFirst server where we need to verify that the user is in our systems. – Irene Marquet May 31 '16 at 12:00
  • You will need a completely different architecture then. This was a very important part of your flow that you left out of the question. This should probably be a different question... – Nathan H May 31 '16 at 12:51
  • yeah thanks, I will open a question describing the problem of combining Basic Auth against a firewall with Mobilefirst authentication – Irene Marquet May 31 '16 at 12:57