Web Security: Images behind Auth
We have a JWT authentication/authorization workflow in our app. The user logs in, the frontend app gets a JWT token as response on successful login, stores it in local storage and uses it for making api calls to the backend rest api by sending it along in Authorization
Header. All was fine, until we realized that images (or other static assets) behind authentication does not work out of the box.
Why is that? Because request for these static assets are generated directly by the browser, and the web app has no way of adding an Authorization
header to these requests, since these requests are not initiated from within the webapp (which means these can’t be intercepted, or altered by the application).
These images are user generated content (specifically, uploaded files) which are considered sensitive information and hence should only be visible to the authorized user.
Basic research lead us to consider these options:
- Signed urls from the server side: The urls are signed on the server side with an impossible to guess token, and are available on an unauthenticated endpoint as long as the server does not expire it. The UI would get these urls and the browser can make calls to fetch image as usual. This has a problem that the endpoint is unauthenticated and if the url is forwarded to another user they would have access to the image as well, if they fetch the image before the server side token expires.
- Signed urls from the client side: URLs with a token sent in the query params.
Since we already have a JWT for the user, we could possibly append it to the image url and the server can verify it from the browser call. This means that the actual user JWT is exposed in query parameters which is a dangerous thing to do.
Or we could make a separate call to the browser to get a image specific JWT and append it to the image url when it is time for the browser to fetch the image. This would need some decision making or synchronization on the frontend on when to make the call to get a token and append it to image url before the browser makes the request. In this case it would also make sense to have tokens with short expiry, so that even if a user forwards that url to someone, the token is not valid anymore. - Get the images within the app as
responseType: blob
, create a blob url from that, and bind it toimg
src at runtime, this allows us to treat the image GET requests as any other requests, and we can send theAuthorization
header. This does not require any changes on the server to sign urls, or to get token from query parameters. - We could store the authentication information in cookies, and since cookies could be sent in browser requests by default, the server could just validate the auth information from the cookie. In our setup, there are 2 problems with this approach: for one, we already have a jwt auth flow based on localstorage, and two, the api runs on a different domain, and we would need to handle cross domain cookies, which could have been a huge effort in itself.
We went ahead with the 3rd approach of fetching the image as blob and binding it to image src. This helped us with quick development by not making any changes on the server for creating signed urls, and we felt it was more secure this way because the url would never contain a token , so even if it was passed around we wouldn’t have to deal with checking short token expiration.
This does have a problem though. This works fine for displaying images within the application, but what happens once we allow the user to download these images? Generally it requires to open up the image url in a new browser tab and the browser takes care of downloading the file once received. This wouldn’t work in our case, as the call from the new tab would not contain Authorization information in header and the download would fail. For this we would probably need to move to a signed URL based solution.