The OAuth / REST approach to web clients is a common and clean approach, and works beautifully with the traditional request/response cycles of retrieving and posting JSON objects. As the application grows, however, inadvertently requirements pop up to download files, or to display some content in a separate window context (eg. iframe). As the OAuth approach uses an Authorization header as default means of authentication, at this point the developer is left to find a “workaround”.
There are several stackoverflow posts covering the topic (eg. this one), but I’d like to show what we’ve decided to do (and what not to do).
- Add the JWT/access token to the URL, as ‘access_token’ GET parameter. Spring Security will pick this up out-of-the-box.
- Use XHR to download content (setting XMLHttpRequest.responseType property to ‘blob’)
- Use a form POST providing a _blank target and passing the access_token as POST parameter
- Set a temporary ‘access_token’ cookie, and remove it once the download has finished
And here’s why we chose to go with option (3):
- The resulting URLs are stored in history and accessible in access logs. Subsequent calls might even pass along the URL as referer. OAuth clearly states that access_token should never be passed as GET parameter
- This loads the entire file into memory, and circumvents the browser download support (Downloads folder, progress bar etc.)
- looks good
- Opens the possibility of CSRF while the cookie is active, and has the potential of a security hole if the cookie is not (always) cleaned up
Using form POST
For downloads, this approach is fairly simple. Instead of providing a classic anchor with target=_blank, we implement a click handler with uses a singleton, hidden form
<div style="display: none"> <form #formRef target="_blank" method="POST" action=""> <input #tokenRef type="hidden" name="access_token"> </form> </div>
and populate the access_token and action in the click handler. The actual implementation is not too relevant here, the main point is to achieve download by posting to a target _blank .
iframes don’t feature very often in modern SPA, but we’ve found use for them when displaying third-party e-mail content. The key approach is described in this stackoverflow post. Instead of setting the iframe#src to your content URL:
- Download content using a normal XHR GET (this is no different from what the iframe would do on setting the src attribute, with the key difference that it happens in the authenticated context of the current window).
- Use URL.createObjectURL to create a local BLOB URL
- Point the iframe src to the URL created in (2)
- Use URL.revokeObjectURL to clean up the local BLOB once it’s not needed anymore
With the approach described above, we never pass our access token as GET parameter, nor do ever set a cookie. The access token is present in the form used to POST the download request, but that can be limited to the time between the user clicking the download button/link (set access token on form) and submitting the form (after which the token can be unset). iframe handling requires some extra work, but there is no network overhead, and buggy cleanup code “only” results in a memory leak, but not a security issue.
In the process of developing funnel.travel, a corporate post-booking travel management tool, I’m sharing some hopefully useful insights into Angular 6, Spring Boot, jOOQ, or any other technology we’ll be using.