After using an http proxy to watch the traffic from my app, it is clear that the app does receive a Refresh token upon successful login, and that after the original session (Auth Token) expires, the app does use the refresh token to obtain a new session. The problem is that when it asks for the new Auth Token, it passes a retUrl that includes the old Auth Token, which is used after the refresh.
I built a test app with just minimal functionality to test the login behavior; if you are interested in the details of my findings I posted them to the Mobile SDK Google+ Group. Note that the test app did not use a custom login screen, which led to some different observed behavior.
The solution is to inspect the startURL
and retURL
params during the community login process and modify them to avoid the loop. The rest of this answer details my process.
First, your community should be configured to use the CommunitiesLogin
VF page provided by Salesforce as its login page (or a custom page based on it). This page, as provided by Salesforce, is a simple redirector that redirects the user to the standard login page for the community. Because I want a custom login page, I have previously just customized SiteLogin
and made that my login page, however, now we need the redirect behavior in order to fix up the params.
CommunitiesLogin
redirects by using the action
param of the apex:page
page tag to call CommunitiesLoginController.forwardToAuthPage()
. The provided method looks like this:
global PageReference forwardToAuthPage() {
String startUrl = System.currentPageReference().getParameters().get('startURL');
String displayType = System.currentPageReference().getParameters().get('display');
return Network.forwardToAuthPage(startUrl, displayType);
}
Note the passing of the startURL
param. When the hybrid remote app tries to login using an existing Auth token, it will call frontdoor.jsp
and pass the app's start page (from bootconfig.json
) as the retURL
, but when the app tries to get a new Auth token via the refresh token, this page will see the request with the original frontdoor
url (including the invalid session token) as the startURL
param instead of the app's start page. Interestingly, the retURL
param from inside that startURL
is also extracted and passed to the VF page, so we can use that.
We need to redirect to our custom login page (in my case, SiteLogin
) instead of the standard auth page, so I added a new method to CommunitiesLoginController
and updated the VF page's action
param to call the new method:
global PageReference forwardToCustomAuthPage() {
map<string, string> params = System.currentPageReference().getParameters();
String startUrl = params.get('startURL');
string extraParams = '';
if (startUrl.contains('frontdoor.jsp') && params.containsKey('retURL')) {
startUrl = params.get('retURL');
extraParams = '&ec=301'; //expected by SDK
}
String displayType = params.get('display');
// update SiteLogin to your custom login page name
string url = Site.getPathPrefix() + '/SiteLogin?startURL='
+ EncodingUtil.urlEncode(startURL, 'UTF-8')
+ '&display=' + displayType
+ extraParams ;
return new PageReference(url);
}
I'm passing startURL
along unchanged, unless it contains frontdoor.jsp
and the page params contain retURL
, which I take to mean that we are in an OAuth Refresh request, in which case we replace startURL
with retURL
. Also note extraparams
. While investigating with my test app, I found that the iOS SDK looks for this param when detecting a session refresh redirect, but I wasn't getting it in my page params, so I'm adding it manually.