I have a Hybrid Remote app built with Mobile SDK 3.1. I've created both an iOS and Android version of the app, and both exhibit the same behavior. To simplify the discussion below, I'll refer to the iOS app.
Background
This app is intended to be used by Community users. The community is setup in the sandbox and is working correctly, with the site prefix /tenant
. The community is configured to use SiteLogin
as the login page. Upon login, the app should load /tenant/mobile_index
.
Steps Taken
I built the iOS app with forceios
. It is configured to use a connected app in my sandbox. The app is essentially exactly the code the was generated by forceios
, with only these changes:
- In
bootconfig.xml
,startPage
is set to"/mobile_index"
. I tried"/tenant/mobile_index"
, which tried to load/tenant/tenant/mobile_index
after successful login. I have also tried/apex/mobile_index
which works fine; after login the app loads/tenant/mobile_index
. Switching between/apex/mobile_index
and/mobile_index
has no bearing on the issue below. In
platform/iOS/MobileApp/MobileApp-Info.plist
, I added the following to replace the standard login page with the community login page, per the Mobile SDK Docs:<key>SFDCOAuthLoginHost</key> <string>dev-orgname.cs8.force.com/tenant</string>
What Works
On first launch, user is correctly presented with the community login page. Upon logging in as a community user, user is correctly presented with mobile_index
page, and the app just works. If you shutdown the app and relaunch, the user remains logged in. Update: debugging the emulator's embedded browser via Safari, I can see that the URL for the login page shown is dev-orgname.cs8.force.com/tenant/setup/secur/RemoteAccessAuthorizationPage.apexp?source=<LONG STRING>
. More on this in next section.
The Problem
However, if you close the app, wait long for the user's session to timeout, and relaunch the app, you are presented with the community login screen. (Aside: I'd really like the app to cache the login and avoid this step. Is this possible?) At this point, if you log in as the community user, you get back an empty login page with no error message.
Looking at the emulator console log, I can see the following events:
Finished load of: https://dev-orgname.cs8.force.com/tenant/secur/frontdoor.jsp?sid=**SNIP**&retURL=https://dev-orgname.cs8.force.com/tenant/mobile_index&display=touch
Then a delay while I type my credentials and submit. Then:
Resetting plugins due to page load.
Failed to load webpage with error: The operation couldn’t be completed. (NSURLErrorDomain error -999.)
Resetting plugins due to page load.
Finished load of: https://dev-orgname.cs8.force.com/tenant/secur/frontdoor.jsp?apv=1&cshc=0000001jupj0000005t8vb&loginURL=https%3A%2F%2Fdev-orgname.cs8.force.com%2Ftenant%2FSiteLogin&refURL=https%3A%2F%2Fdev-orgname.cs8.force.com%2Ftenant%2Fsecur%2Ffrontdoor.jsp&retURL=%2Ftenant%2Fsecur%2Ffrontdoor.jsp%3Fsid%**SNIP**%26retURL%3Dhttps%253A%252F%252Fdev-orgname.cs8.force.com%252Ftenant%252Fmobile_index%26display%3Dtouch&sid=**SNIP**&untethered=
Resetting plugins due to page load.
Finished load of: https://dev-orgname.cs8.force.com/tenant/secur/frontdoor.jsp?sid=**SNIP**&retURL=https%3A%2F%2Fdev-orgname.cs8.force.com%2Ftenant%2Fmobile_index&display=touch
It certainly appears that the login is successful, but then the loading of mobile_index
fails with (NSURLErrorDomain error -999.)
. I've found some references indicating that the iOS web view throws this error when a page load is interrupted by another page load, but I'm not sure how that's relevant; I'm using the OOB Hybrid app framework. Shouldn't that be able to log in to the org a second time? Once this occurs, the only solution is to uninstall and reinstall the app.
Update As mentioned in the update to the "What Works" section, log in during initial launch goes to /tenant/setup/secur/RemoteAccessAuthorizationPage.apexp
, but as shown here, when re-launching after a timeout, the log in page is /tenant/secur/frontdoor.jsp
. Why? should this endpoint be avoided for a community login? How do I fix in the mobile app?
Other possibly relevant items
- As mentioned above, the same thing happens with the Android app.
- So far, the apps are only being run in emulators. Awaiting my iOS dev account, and awaiting Android hardware. Note: happening on device also; see update 2 below.
- Not sure I understand the
oauthRedirectURI
in the context of a hybrid app. In my connected app, I made up a URI as suggested, (sfdc://orgname_ios
), and put the same in mybootconfig.json
. - While logged in, if you log out, you can log back in. It only seems to be when the logged in session times out that this issue occurs. Trying to log in as a different community user also fails; it doesn't appear to be tied to the user whose session timed out. Update While logged in, if you log out, you get
/tenant/setup/secur/RemoteAccessAuthorizationPage.apexp
. - The mobile app content is HTML5/Angular/Ionic served via VF Page, and uses VF Remote Actions to communicate with the server. Based on the section "Managing Sessions in Hybrid Apps" on p61 of the Mobile SDK docs pdf, I'm expecting that I'll need to handle session refreshes during Javascript Remoting calls using code similar to that found in subsection "JavaScript Remoting in Visualforce" starting on p62, but I haven't added that yet. However, this is a separate issue that happens when the app is not running, the session times out, and the app is relaunched. Note I have added this code, no effect. See Update 3 below.
Update: As requested, my bootconfig.json. Lightly sanitized to remove client information.
{
"remoteAccessConsumerKey": " **SNIP** ",
"oauthRedirectURI": "sfdc://orgname_ios",
"oauthScopes": [
"web",
"api"
],
"isLocal": false,
"startPage": "/mobile_index",
"errorPage": "error.html",
"shouldAuthenticate": true,
"attemptOfflineLoad": false
}
Update 2: installed on Android device, same behavior as emulator. Initial launch & login works fine; coming back hours later (after session times out) and re-launching, I get login screen; logging in just takes me back to the login screen with no error message. Just installed on iOS device; will test in a few hours after session times out.
However, in testing this, I learned something new. The first time I tried to log in after the session had timed out, I fat-fingered the password. The login page correctly displayed the message that my username/password was not recognized; upon correcting the PW, I was taken back to the login page, this time with no message. I think this confirms my suspicion that the login page is successfully completing a login, but the app for some reason isn't navigating to the start page correctly.
Update 3: I've added code that wraps my VF remoting calls, and detects if the session has timed out. If I catch the error I use com.salesforce.plugin.oauth to re-authenticate and retry the VF remoting call. This is working just fine now (and console logging proves the timeout and the refresh), but the login-after-launch-after-timeout issue persists, and the only solution remains to re-install the app. See also the inline updates above about RemoteAccessAuthorizationPage
vs frontdoor.jsp
.
Update 4: I've updated Cordova to 4.3.0 and cordova-ios to 3.8.0 (both latest), it does not resolve the problem.
Using @xn.'s suggestions from the comments, I've been using mitmdump/mitmproxy to watch what happens on a fresh install launch vs. a relaunch after timeout. So far I've gleaned a few things:
- On first launch (after install), the first http request is for
.../tenant/services/oauth2/authorize?client_id=<ID>&redirect_uri=sfdc://orgname_ios&...
, which redirects toRemoteAccessAuthorizationPage
. - On a relaunch, no call is made to
/tenant/services/oauth2/authorize
; instead it just callsfrontdoor.jsp
directly as outlined above. This request includes asid
param in the url. - The login process redirects to
frontdoor.jsp
a few times; each time the samesid
param is included. - using
mitmproxy
, I intercepted the initial call tofrontdoor.jsp
and removed the sid param. This fixed the problem.
So, something is going wrong on relaunch when the request includes an expired session id, but I don't know how/why, or how to prevent.
Update 5: Based on my earlier findings with mitmproxy
and the sid
param, I tried to address the issue in my login page by detecting and removing the sid
param in the page's action
method. This met with partial success, and some surprising results. The summary:
In
SiteLogin.jsp
, in theapex:page
tag, I addedaction="{!clearOldSid}"
In the controller,
global PageReference clearOldSid() { PageReference ref = ApexPages.currentPage(); if (ref.getParameters().containsKey('sid')) { return Page.stnhg_index; } else { return null; } }
Note that I tried modifing
ApexPages.currentPage()
and returning that, but the system kept putting thesid
back into the url upon redirect.This worked partially. It did solve the "cannot login after re-launch after session timeout" issue, but it introduced a new issue. Previously, quitting the app (via iOS multitasking menu) and relaunching would reopen the app without a login page. After the above
sid
-stripping fix, every relaunch resulted in a login page. Apparently during relaunch, SiteLogin is being loaded even if it isn't displayed. (aside: just realized, that's probably the Mobile SDK in action, trapping the login page and refreshing the session). In trying to figure out what was going on, I tried modifying theif()
test in my action method to only clear the sid when the page runs as guest user:if (ref.getParameters().containsKey('sid') && UserInfo.getUserType().contains('Guest')) { ...
Surprisingly, this worked a little better - Fresh install, login, quit, relaunch opens the app without showing a login page; quit, timeout (or kill) session, relaunch shows the login page, but it works. However, quit & relaunch at this point (don't kill the session) takes you to the login page again, unlike first launch. Is this good enough? Not really, but at least the app doesn't die forever after the first timeout. It's also certainly not the right way to handle this.
Investigating this last behavior led me to take a closer look at the sessions listed in Session Management, where I found something very interesting. Upon a first launch, these are the sessions that get created after logging in:
After killing those sessions, relaunching, and logging in, these sessions exist:
Different number and type of sessions. I don't know if this is because of my 'clear the sid' hack, but I doubt it, since that still gives a different behavior on relaunch. I suspect it has something to do with the different launch behavior on any relaunch. At this point, it seems like the root cause is that calling frontdoor.jsp with an expired session isn't working like it should.
NSURLErrorDomain
specifically refers to the page load being cancelled. Hard to say why the SDK would be interrupting the page load, though.forceios
. On launch, the app navigates to the startPage, showing the login page if necessary. The only thing I see in Mobile SDK docs about Hybrid apps & session management is the section on p61 that I reference above.cordova platform list
tells me I have ios 3.7.0, which includes the pull request for the bug you cited. I also openedCordovaLib/Classes/CDVWebViewDelegate.m
in my xcode project and confirmed that I have the code shown in the commit. I do however see the cordova-ios 3.8 just shipped, so I may try updating my cordova and cordova-ios to see if it helps.