Securely load custom content in web page

27394

I have a requirement to load a custom developed web page in an iframe in a page on a Collaborate site. The custom web page is hosted in Azure and contains confidential information. Normally I would implement authentication on the custom web page to prevent unauthorized access. However, our business is requesting that users not be required to enter a separate set of credentials because they have already logged into HighQ.

It is a hard requirement that access to the custom page should be locked down to authorized users, but paradoxically, the users must not be required to log in. I need to find a way to make it so that my custom page can only be accessed via this particular iframe on the HighQ page. How might I go about this?

One thought I had was to implement some kind of token system and append a token to the iframe src URL. Does HighQ have a way to securely store such values so that it wouldn't have to be hard coded into the page source?

  • Hi Laura Jacobsen, not easily, though it can be done. Before replying with a lengthy response, is this custom page yours? or a third-party? and is it dynamic, i.e. a server generated page, or is it a static page?

    Other than that, the only out-of-the-box HTTP options can offer is X-Frame-Options , but this isn't ubiquitous and only allows you to limit rendering the contents within a certain (request) domain, i.e. https://myinstance.highq.com, to say nothing about who is requesting (which user). 

  • Andrew Quinn Yes, this is somewhat complicated.

    The page is a custom page with dynamic content. It's an application that I developed in the .NET framework, hosted in Azure.

    I looked at X-Frame-Options but that doesn't fulfill our needs. Like you said, X-Frame-Options only controls if the browser is allowed to render the page in an iframe. It doesn't provide any security if the page is rendered in a regular browser window, not in a frame. The page would still be unprotected if a hacker guessed its URL. It's a hard requirement that our custom content be protected behind some type of authentication.

     

  • Hi Laura Jacobsen,

    This process will require using the Collaborate API in your web app, and will require you generating API tokens for the web app to use.

    The basic idea is that we create an iSheet (in your site) where the permissions are set so that users only have access to their own iSheet items. We then start out (in a script) by creating a request to your web app (with some request meta-data, i.e. what you want to access, and including user ID), your web app will then challenge this request, by impersonating this user and creating a record in the iSheet that will contain a unique access token (with a time-to-live). Your web app responds to the request asking for challenge response. The script then queries the iSheet to retrieve the access token with the time period, and posts back to your web app. You can then decide how you want to proceed if the access token matches the token you created.

    It's clearly a little bit of work, but can be reused for other use-cases. I can also help you with certain things if you get stuck. One example is the script on the Collaborate-side will need to query an iSheet, which will use the Sheet view - export XML link functionality. I have an example JavaScript wrapper class to help make that easier, if you've never used the export XML.

    I've started out by discussing the basics to give you an idea of what would be involved. If you're interested, I can start to provide more detailed information.

  • Andrew Quinn Thanks, I get the gist of how this works. I'd appreciate any sample code or more detailed information if you already have it to hand.

  • Here are some more details:

    Site Group

    Create a Site Group (e.g, "Access Tracker Group") for the user to be granted access to functionality

    External Access iSheet

    iSheet:

    Name Access Tracker
    Access Type Private
    Display in iSheet list No

    Permissions:    

        Enable permissions, and set View/Edit both to "Own" only, for "Access Tracker Group"

    Columns:

    Request ID SingleLineText
    Request Resource SingleLineText
    Request IP Addr SingleLineText
    Status Choice (Challenged, Granted, Denied, Error) - default Challenged
    Access Token

    SingleLineText/MultipleLineText

    depends on what constitutes your access token, is it

    Resource Data MultipleLineText
    Resource Accessed

    Date and Time/SingleLineText

    depends on whether you need TZ and/or secs resolution

    Views:

    Name Client (Challenge)
    Columns
    • Request ID
    • Request Resource
    • Access Token
    Sort "Modified date" DESC
    Filter
    • "Modified date" is greater than "Today" -1 Days
    • AND "Status" is equal to "Challenged"

     

    Name Client (Response)
    Columns
    • Request ID
    • Request Resource
    • Access Token
    • Resource Data
    Sort "Modified date" DESC
    Filter
    • "Modified date" is greater than "Today" -1 Days
    • AND "Status" is not equal to "Challenged"

    I've included (attached) a script library that contains some example helper functions for the Collaborate-side*.

    So, say the (Collaborate) Site is #1234, iSheet is #45, and the views #67, #89 respectively.  And, you had a "resource" on your webapp called "ExampleAppFrame" (whatever that may be; JSON data, image, or a web page), then the page in the script would look something like:

    var access = CommunityExamples.AccessTracker({
        siteID: 1234,
        authoriseEndpoint: "" target="_blank">http://localhost:8080/requestResource",
        authoriseResponse: "" target="_blank">http://localhost:8080/requestResourceResp",
        vwClientChallenge: {
            siteID: 1234,
            sheetID: 45,
            sheetViewID: 67
        },
        vwClientResponse: {
            siteID: 1234,
            sheetID: 45,
            sheetViewID: 89
        }
    });
    
    access.getResource("ExampleAppFrame").then(function(url) {
        $j("iframe#myAppFrame").attr("src", url);
    });

    The "authoriseEndpoint" is where we send the initial request for access to the resource, and the "authoriseResponse" will be where we send the Challenge-Response (the access token).  These can be the same endpoint, it just depends how you built out your server-side.

    We call the "getResource" method with the resource ID we are requesting, which will be sent to the authoriseEndpoint, along with a unique request ID.  If the request is successful, we will receive details of how to access the resource in the resolved Promise (JQuery Deferred).  Note: I've used JQuery Deferred here instead of the usual Promises, as those need polyfilling for Internet Explorer.

    * The code has limited, or no bullet-proofing as these are offered as examples, or samples.  I cannot offer production-ready libraries without compromising the Intellectual Property of my company. 

  • Server-side:

    "authoriseEndpoint": The request is received:

    • Check user id has access to site (GET /api/3/sites/{siteid}/users
    • Get sheet details for the "Access Tracker" sheet to obtain column id's - this way the process can be site-agnostic, but you could use configuration settings if time/simplicity is more important
    • (optional) check user is in the Site Groups you require, i.e. you could create more Site Groups that allow different levels of access to resources
    • Impersonate user (GET /api/1/admin/impersonate) and add iSheet item:
      • supplied Request ID
      • supplied Request Resource
      • (optional) request IP address
      • Status will be defaulted to "Challenge"
      • a unique access token, can be as simple as a GUID, or some cryptographic-generated token, i.e. generated from the supplied data
    • Once record created, respond to caller and echo back the Request ID

    "authoriseResponse": The challenge-response is received:

    • Query iSheet for valid entry in iSheet, where Status = "Challenged" and Request ID/IP Address/User/Site is the same, and Modified Date is within your TTL limits.
    • Match the token provided with that in the iSheet record, optional also match against a newly generated token using the same technique as before
    • If all passes, update iSheet record with Status = "Granted", and information about accessing the resource, i.e. url with some generated querystring parameters, https://myserver.com/resources?requestID={requestID}&resourceID={some generated resource token}

    Your "resources" endpoint can query the iSheet to match the request ID/IP Address/resourceID, where Status = "Granted"

    • Update the iSheet record status to "Accessed" along with the Accessed DateTime stamp
    • Now respond back to caller with the resource whatever that may be.

    I'll leave it here for now, as I'm sure you'll have more questions,

    Andrew

  • Andrew Quinn thank you very much for providing such detailed guidelines. Your contribution to the community is always appreciated.

  • Laura Jacobsen, I've noticed there was a typo in the attached script library (Line 60) I uploaded, which should have read:

     

    "userID": +window.collab_common_userID,

     

    IntelliSense sometimes makes me anything but ;) I've replaced the attached script library with the amendment

  • Andrew Quinn Thanks, this is a great help. I thought you worked for HighQ but Imran Aziz's comment makes me think that you are not an employee, just a wealth of information. I really appreciate it.

  • Laura Jacobsen I have a client firm that is looking for a very similar outcome. Can you share back here how you solved your auth and security concerns? Any reflections, hints, or learnings to share? Thanks!