53

I have a client-side application on domain client-domain.example and a server-side application on domain server-domain.example. There is an API on the server-side. The client-side application sends AJAX requests to the server-side application. I use token-based authentication, so the client-side application sends token in headers with each AJAX request, for example: "Authorization: Bearer {some token}". It works fine with AJAX requests, when I need to get or post some data.

But the server-side API also keeps files. For example images. The files are private, only authenticated users can get them. And I need to show this images on the client-side in <img> tag. I can't get them using <img src="http://server-domain.example/path/to/image"> because in this case browser will not send Authorization header to the server-side.

What is the adopted solution? How client applications load images from server-side API?

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Ildar
  • 3,808
  • 7
  • 45
  • 81
  • 1
    Good answer here http://stackoverflow.com/questions/4285042/asychronously-load-images-with-jquery/12714338 Imho, the best - to use browser cache in the second answer ( am not sure the solution is cross-browser) – vitalygolub Dec 04 '15 at 20:40
  • 1
    I think I answered this in your other question here: http://stackoverflow.com/a/34112350/18044 – MvdD Dec 05 '15 at 23:58

2 Answers2

43

There are three methods to solve it, the best approach to solve it is using the signed URLs

1. signed URL (can be insecure)

The first method simply creates a route without authentication (anonymous access) with a signature hash parameter that indicates if the resource can be loaded or not.

<img src="http://server-domain.example/path/to/image?guid=f6fc84c9f21c24907d6bee6eec38cabab5fa9a7be8c4a7827fe9e56f2">

When the server receives the request it must validate the guid if the expiration time has not been reached and, of course, check if the guid has a valid signature.

This approach is used by several files/documents servers like Dropbox, S3, CDN providers, etc.

See the technique in some companies.

SECURITY:

  • the guid can not be just UUID of the image/user, because this doesn't provide any protection.
  • the guid can not be the same token you use for authentication (for example, you can't use auth-JWT tokens), because the user can share the link - and the user will share his tokens (see also (2)).

as mentioned above: guid should have a server-side mechanism of validation (date/signature/...) and should not provide more permissions than "access to the requested file"

2 Query String with JWT (most probably a security breach)

The second method is to pass the token by querystring with the image URL.

  • This method is not recommendable because it exposes clearly the URL and many servers sometimes write and expose public logs of URL accessed. The bad notice is that the JWT exposed normally the user can get control a lot of features further the image load.
<img src="http://server-domain.example/path/to/image?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c">

When the server receives the request you must validate the token by querystring and response with the content.

SECURITY NOTES: worse than (1) - because now authentication info (JWT auth) is exposed in the URL and can be cached/logged by servers OR accessed by any server in the middle OR the user can simply share the "image link" with their colleagues.

But if JWT is NOT an access token, but a one-time token generated specifically for accessing that particular file in a form of JWT then it provides the same level of security as (1).

3. cookies

The third method creates an authenticated cookie to validate the access of the image.

  • This method is not recommendable because is out of API pattern (webapi/token based authentication in general).

When the server receives the request you need to validate if the validate cookie is valid.

SECURITY NOTES: if you can provide security for your cookies and XSS and CSRF — are not just letters for you then it is a solution. But keep in mind: cookies are sent by the browser automatically with each request. Much more information about possible threats and solutions: Where to store JWT in browser? How to protect against CSRF?

maxkoryukov
  • 4,205
  • 5
  • 33
  • 54
Jeferson Tenorio
  • 2,030
  • 25
  • 31
  • 2
    Can you explain why option 2 does not protect against XSS and presumably why option 1 does? In option 1 how do we generate and validate the guid? – srayner Mar 28 '19 at 21:52
  • 1
    What is the difference between 1 and 2 ? Doesn't the token in 2 serve the same purpose as the guid in 1 ? – gaurav5430 Jun 10 '19 at 18:56
  • @gaurav5430, the difference between 1 and 2 is The first case the GUID is a record in the Database like a custom solution, the second is based on JWT (industry standard RFC 7519) authentication method – Jeferson Tenorio Jun 11 '19 at 11:20
  • 1
    @srayner you are right, both cases have the same level of security. To generate the guid you must to use any technique to make UUID and to validate you will need to do it using a custom solution (using DB for example). – Jeferson Tenorio Jun 11 '19 at 11:51
  • 2
    @JefersonTenorio so, in the first case any (authenticated/unauthenticated) user can access the resource while the guid is valid? are there any example implementations of the first approach? I looked at the links but they seem very specific to the ecosystem they talk about. – gaurav5430 Jun 12 '19 at 05:22
  • 1
    The difference between options 1 & 2: Option 1 uses key specific to a shared resource (image), whereas option 2 uses JWT token, used for accessing all resources on a server. In other words: UUID from option 1 is bound to resource and time, hence cannot be used to access other images/APIs on the same server. This is where XSS protection comes from for option 1 – Timofey Chernousov Jun 09 '21 at 01:21
  • actually both 1 and 2 are subjects of the same security threats. The key difference here is: what kind of `token` or `jwt` is included in the URL. If the `token` contains only info necessary to access the resource (and only this resource) and is limited in time - then even having a clear `token` the malicious user will not be able to do anything except getting access to that file (for some time). In terms of "get access to file" both (1) and (2) provides the same level of security. In terms of "protect other parts of API when the file is compromised (token is exposed)" - JWT is worse – maxkoryukov Sep 24 '22 at 09:33
2

My solution to basically this exact same problem, based on Jeferson Tenorio's answer below (option 1), was to sign the URL to my API call with an encryption of the image and the user's JWT token, e.g. path/to/image?token=xxxx. In laravel this is easily accomplished with encrypt($your_object) and decrypt($token) (https://laravel.com/docs/5.7/encryption), and then I used the extracted token to verify the user had access to the file in question. But there are probably many other libraries capable of handling this.

I would be curious if there are any security concerns, but from my perspective the JWT is never exposed via plain text and the encryption relies on a secret key that malicious actors shouldn't have access to, so it seems like it should be fairly secure. My only real complaint is that the token is quite long using this method which does not make for presentable URLs.

Kyle Crossman
  • 645
  • 6
  • 12
  • This feels like option 2 from Jeferson's answer above. It feels very easy to impliment, but as you asked, are there any security implications? – srayner Mar 28 '19 at 21:55
  • this may be useful. https://stackoverflow.com/questions/32722952/is-it-safe-to-put-a-jwt-into-the-url-as-a-query-parameter-of-a-get-request – srayner Mar 28 '19 at 22:01
  • @srayner Main difference is that I don't expose the JWT token in plain text which was his issue with option 2. Instead I'm encrypting an object containing the JWT token as well as the name of the file in question as the token for the file URL, which can then be decrypted to access the file by validating both that the JWT is valid and that the file name matches up with the file being requested. Thus the encrypted token is only useful for the file in question and could not be used to gain access to other files. You could take it a step further and put in the user info as well to prevent sharing – Kyle Crossman Mar 29 '19 at 13:47