Rich media in task descriptions eventually break with 403 error due to AWS security token expiring

I have a tangential point to the discussion from Project attachment image link expires after some time

The Situation

When images are included in the Asana task descriptions, it seems their temporary AWS source URL is used. I do see in the image request the query parameter X-Amz-Expires: 119, indicating that the request is only good for 120 seconds (2 minutes).

The Problem

I would like to cache the Asana tasks for a substantial amount of time to reduce requests and improve performance of my application. Currently, I default my own cache TTL to 15 minutes. This, of course, means the images eventually break with a 403 error.

What’s especially troublesome with this behavior is that the image source URL can expire if the user has been on my application’s webpage for longer than the 2-minute AWS token expiration. I can load fresh data from Asana on page load, but the image request is then 403 if the user tries to view the image in the browser after sitting on the page for more than 2 minutes (which is expected user behavior).

Here are some images for context:

My application with the images properly displayed in the task description

My application with the images broken due to HTTP 403 responses

Potential Solutions

I would like to know the Asana Community’s recommended approach to caching task descriptions that include inline rich media. I want to make sure I’m not missing something in the Asana API or otherwise before I change the behavior of my application.

From what I’m observing, it seems the best solution is to request task descriptions on an as-needed basis and just-in-time. This would use more API requests, but it would optimize security and data privacy.

I prefer not downloading and caching the image assets myself since that would use more server storage and require asset management logic (which the Asana API already affords me via their own AWS buckets).

1 Like

The Asana API documentation about getting attachments notes the distinctions among the various media attachments within a task:

Note that within the Asana app, inline images in the task description do not appear in the index of image thumbnails nor as stories in the task. However, requests made to GET /attachments for a task will return all of the images in the task, including inline images.

This means I could cache the task descriptions for longer while refreshing the attachments more frequently.

I’m still investigating an appropriate solution…

Additionally, the Asana API documentation on Attachments does denote the 2-minute URL expiry for attachments’ download_url, suggesting they not be cached:

If present, this URL may only be valid for two minutes from the time of retrieval. You should avoid persisting this URL somewhere and just refresh it on demand to ensure you do not keep stale URLs.

So it does seem best to request Asana attachments as-needed for just-in-time loading.

Since the user’s browser should handle caching of the image file once first requested, I shouldn’t need to repeatedly request a fresh download_url for the attachment while the user continues to explore the current webpage.

To handle this, I’m thinking of simply fetching a fresh download_url by using the onerror HTML event of the <img> tags. Asana graciously includes data-asana-gid on the <img> element in the task description, which means I can just fetch it fresh whenever the browser fails to load the image.

The problem with this strategy is that the browser will make two requests for each expired attachment, which will be the most common case considering my 15-minute TTL to the attachment’s 2-minute TTL.


I don’t know if it’s something you can implement, but my suggestion, is to create a proxy.

In your app, you should replace asana urls with “/asanaAttachment/xxx”, that will download the file server-side.
And from that proxy, you can return a max-age of any time.

You can also keep a server-side caching for a while if you need, but you can also only return data bytes to the caller without any server-side local cache.
Your browser will keep the file as long as you want, based on the max-age.

Using a proxy will also help you manage all CORS issues.

You may send required data and token to your proxy as headers of your GET.

1 Like

Ah, yes, of course! This sounds perfect and would definitely be the optimal solution!

I really didn’t want to modify the data that I get from Asana, like replacing img src URLs within the task html_notes, but I think it makes sense to do in this case. The original URLs really just aren’t ideal in my case, as you’ve suggested.

In the meantime, I’ve had the onError handlers refreshing the client-side’s record of the attachment’s data from the server side. My hesitation is for handling the various types of media and the difference between view_url and download_url.

@Frederic_Malenfant Do you know how to authenticate the requests for the attachment.permanent_url field? I was wondering if that’d just always give me the proper HTML representation of an attachment, but it seemed like basic authentication with a PAT didn’t work when performing a curl on the command-line. The response was the Asana app’s login screen, which makes me think those URLs require OAuth authentication. Perhaps they are intended for in-app Asana add-ons rather than external applications like mine, though.

I don’t know,
we never use PAT in our application, only OAuth tokens.

FYI—I’m proxying the AmazonS3 asset requests and it’s working like a charm, including proper browser client-side caching as expected. :white_check_mark: