none
Hosting a custom Login page to automatically renew an ACS token in MVC3

    General discussion

  • I have an application protected by ACS, using the standard suite of Identity Providers. Users keep the application open for days at a time, and they were annoyed with the requirement to click the provider button every 24 hours (the maximum token lifetime in ACS -- see http://msdn.microsoft.com/en-us/library/gg185906.aspx#BKMK_5).

    So, my primary goal was to "automatically" click the button for the user. (This "automatic" behavior is standard in a vanilla ADFS2 implementation, so it seems like it should be an appropriate custom implementation in ACS.) As a side benefit, it also signs in automatically when the application is accessed in a new browser session.

    This isn't terribly difficult to do, but the information for doing it is spread across a dozen documents and forum entries. I've consolidated the information here in the hope that it helps someone else. It seems like this should be easier than I show here; maybe someone else can chime in. (I haven't investigated using the WIF Session token lifetime as a workaround.)

    To do the "automatic" click I created a custom login page. See Option 2 in http://msdn.microsoft.com/en-us/library/windowsazure/gg185963.aspx.

    I modified the standard login page by adding the following 6 lines of code in ShowSigninPage function, just before the existing loop over identityProviders:
    =============================
        for (var i in identityProviders) {
            if (cookieName !== null && cookieName == identityProviders[i].Name) {
                window.location = identityProviders[i].LoginUrl;
                return null;
            }
        }
    
        // Loop through the identity providers
        for (var i in identityProviders) {
        ...
    
    =============================

    To automatically redirect to this page when the user attempts to navigate to your application, a few more steps are required.

    (There appears to be a missing piece in the sample login page generated by the ACS portal; the references to "wctx" in the following are in response to that oversight. See the discussion at the bottom of this post for details.)

    Create a Controller/View for the SignIn page (I used SignIn everywhere instead of login). My SignIn controller looks like this:
    =============================
        public class SignInController : Controller
        {
            public ActionResult Index(string wctx)
            {
                return View(new SignInModel(wctx));
            }
        }
    
    
    =============================

    To redirect to this page instead of the default ACS login page, add this method to global.asax.cs:
    =============================
        protected void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e)
        {
            e.SignInRequestMessage.BaseUri = new Uri(e.SignInRequestMessage.Parameters["wtrealm"] + "SignIn");
        }
    
    =============================
    Note: By using the wtrealm parameter to construct the Uri, this works for development, staging and production environments.

    Of course, the user can't yet get to the new SignIn page because all pages are protected by ACS. To unblock this one page, add the following to web.config (right near the similar block for FederatedMetadata):
    =============================
      <location path="SignIn">
        <system.web>
          <authorization>
            <allow users="*" />
          </authorization>
        </system.web>
      </location>
    
    =============================
    Note: If the custom SignIn page uses your application .css file or images, you also need to add similar "allow" sections for "Content" and "Images".

    Finally, modify the HRD metadata script in the custom SignIn page so that it also works for development, staging and production.
    I used a strongly typed view, so created this MVC3 model:
    =============================
        public class SignInModel
        {
            public string Realm;
            public string Wctx;
    
            public SignInModel(string wctx)
            {
                Realm = FederatedAuthentication.WSFederationAuthenticationModule.Realm;
                Wctx = System.Web.HttpUtility.UrlEncode(wctx ?? "");
            }
        }
    
    =============================
    and the script at the very bottom of the SignIn page (shown here url-UNencoded; leave the original url-encoded script):
    =============================
        <script
            <script
            src="https://grandview.accesscontrol.windows.net:443/v2/metadata/IdentityProviders.js?protocol=wsfederation&realm=@Model.Realm&reply_to=&context=@Model.Wctx&request_id=&version=1.0&callback=ShowSigninPage"
        type="text/javascript">
        </script>
    
    =============================

    Missing "wctx" bug(?): The normal WIF redirection to the SignIn page includes a querystring parameter called wctx. In the ACS-hosted login page, this wctx parameter is passed on to the IdentityProviders.js script as the Context parameter (causing it to end up in the LoginUrl of each provider). The sample custom login page generated by the ACS portal explicitly sets the context parameter to empty (at least it did when I used it). The consequences of this were two for me: 1) any querystring parameters the user might use in trying to connect to my application are lost, and 2) the final request to my application was a POST instead of a GET (the normal sequence is a POST, which returns a 302, followed by a GET). Thus, if the user does an F5 refresh at that point, they get an unnecessary "Resend" warning (and on every refresh thereafter!).
    Friday, September 23, 2011 1:26 AM

All replies

  • It's great to see all of this in one place, but I can't help but think it makes way more sense to just extend the lifetime of the session token upon initial log in?

    If the user is staying put within the application for multiple days, it seems like they would also like to stay on the page they were on for that entire time with the data untouched. Using the JavaScript to redirect to the IdP and login would take you away from the page and the open data et al. No?


    Developer Security MVP | www.steveonsecurity.com
    Friday, September 23, 2011 3:56 AM
  • but - the token expiration setting in ACS only controls the initial timeout. After that the token gets replaced by a WIF session token.

    From there you can implement things like sliding expiration etc.

    So IMO there is no need to do a roundtrip to ACS to keep the token alive.


    Dominick Baier | thinktecture | http://www.leastprivilege.com
    Friday, September 23, 2011 7:10 AM
  • Note: Except for the 6 lines of code added to the custom login page, I believe all of the rest is required to host a login page in general (although the controller-attribute-based approach for protecting individual controllers is an alternative to the "location...allow" configuration).

    The "automatically renew ACS token" approach appears to have benefits over extending the WIF session life. I'd be interested in a discussion of the disadvantages.

    Caveat: My application is decidedly non-traditional. I'm using ACS simply to offload authentication; ACS doesn't provide, and the user doesn't enter any sensitive data that needs protecting. My only goal is to simplify the user's sign-in experience.

    Session tokens traditionally have short lifetimes, usually sliding, for various security reasons. My users typically click a few times, then go away for a few hours or days, then want to refresh to see the latest data. To use a session token in this case, the token would need to have a very long lifetime (days or weeks or a year?). Feels wrong?

    The Fedauth cookies are in-memory by default (can/should these be persistent?). Therefore, if a new browser session is started, without the automatic ACS renewal, the HRD button comes up again. The ACS renewal approach takes the user directly to the application instead (with a behind-the-scenes roundtrip to ACS, of course).

    I haven't used ADFS2 in a few months, but as I recall, the behavior of this "ACS renewal" HRD page is more similar to the ADFS HRD page; the standard ACS HRD page is quite different. That is, if a new browser session is started with ADFS, the user is automatically authenticated with their prior HR choice, rather than requiring them to click a button to re-confirm that choice.

    Any thoughts?

    Bill

    Monday, September 26, 2011 7:59 PM