4

I have a ReactJs application that retrieves an S3 URL from the API and get a pre-signed version of it using the Amplify Storage library.

This URL is the source of a video, the code is something like this:

           Storage.get(
             outputVideoSrc,
            { expires: 43200 }
           ));

But I have noticed that even tho the expiration time is really long, after ~1 hour the video stops playing, showing a message of Network error and aborting the playback.

If I get the video URL and try to access it from my browser I see an error similar to it:

<Error>
<Code>ExpiredToken</Code>
<Message>The provided token has expired.</Message>
<Token-0>
...

After some googling, I found that this expiration is due to the authentication token being expired, not the pre-signed URL per se. However, I need this URL to work for more than 1 hour, so the user can work with the video for long period of time.

It seems that it is not possible to customize how long the token will last on Amplify: https://github.com/aws-amplify/amplify-js/issues/2714

I have also tried to keep reloading the video after some time in the hope that I get a refreshed token, but:

  1. It doesn't work
  2. Even if it works I need to reload the video, which is terrible UI

Which alternatives do I have in order to make this work and not having the video stop playing all the time?

dfranca
  • 5,156
  • 2
  • 32
  • 60

1 Answers1

1

The only way I have found to fix it is to use a lambda function to pre-sign the URL and use it to resolve a GraphQL field on my schema.

So, on using the amplify-cli I have added a new function

amplify function add

In the example here I'm naming it getUrls

My function has a code similar to this:

import boto3
from botocore.exceptions import ClientError

s3_client = boto3.client('s3')
bucket = 'my-bucket'

def handler(event, context):
  print('received event:')
  print(event)

  urls = []

  if 'typeName' in event and event['typeName'] == 'MyType' and event['fieldName'] == 'urls':
    print('Requesting field keys for MyType...', event['source']['keys'])
    for key in event['source']['keys']:
      try:
          signed_url = s3_client.generate_presigned_url(
            'get_object',
            Params={
              'Bucket': bucket,
              'Key': key,
            },
            ExpiresIn=86400  # 24 hours
          )

          print('Signed URL: ', signed_url)
          if signed_url:
            urls.append(signed_url)
      except ClientError as e:
          print('Error', e)

  return urls

Don't forget to add permission to S3:GetObject on the bucket for your function on getUrls-cloudoformation-template.json Add on your PolicyDocument:

                        {
                            "Sid": "S3",
                            "Effect": "Allow",
                            "Action": [
                                "s3:GetObject"
                            ],
                            "Resource": "arn:aws:s3:::mybucket/*"
                        }

Then add it as a resolver for your field on the schema.graphql

type MyType @model
{
  id: ID!
  keys: [String!]
  urls [String!] @function(name: "getUrls-${env}")
}

That is it, when you request for the field urls it will return the pre-signed url valid for 24 hours, you can then change it to any duration you want.

dfranca
  • 5,156
  • 2
  • 32
  • 60