Direct Upload to Amazon AWS S3 Using PHP & HTML

As we all know, Amazon S3 is a cost-effective, reliable, fast and secure object storage system, which allows us to store and retrieve any amount of data from anywhere on the web. Today I am going to show you how you can directly upload any file to Amazon AWS S3 using HTML Form and PHP without compromising your own server security and upload latency.

Create User and Bucket

  1. First step is to create a bucket by visiting AWS S3 Console, this is where we will be uploading files. Note down its name and AWS region.
  2. Create a IAM user profile by going in your AWS Management Console, after user creation you should be presented with unique Access Key ID and Secret Access Key, you should write them down as we will need them in our PHP code.
  3. Now create a new Policy for user in Create Policy Console (Or Copy-paste Policy Document below) and attach the Policy to IAM user you’ve just created.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "s3:GetObject",
                    "s3:PutObject",
                    "s3:PutObjectAcl"
                ],
                "Resource": [
                    "arn:aws:s3:::*"
                ]
            }
        ]
    }

    You can also create IAM policy quickly using policy generator.

  4. Now create another “S3 Bucket policy” for your bucket using policy generator. go back to the bucket you’ve created, click its properties, scroll down to permissions tab and click edit bucket policy, now copy/paste policy text you’ve just created. Eg:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AddPerm",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::your-bucket-name/*"
            }
        ]
    }

HTML Form

Once everything is set correctly in AWS console, we can now create an HTML form that can upload content to Amazon S3 directly. But as you can see there are values in the form fields which needs specific values, such as Signature field (X-Amz-Signature) it requires a SHA256 calculated signature, and Policy field requires Base64-encoded policy string. Let’s generate these values in next section of this tutorial.

1
2
3
4
5
6
7
8
9
10
11
12
<form action="http://<BUCKET>.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="${filename}" />
<input type="hidden" name="acl" value="public-read" />
<input type="hidden" name="X-Amz-Credential" value="<--ACCESS_KEY-->/<--SHORT_DATE-->/<--REGION-->/s3/aws4_request" />
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Date" value="<--ISO_DATE-->" />
<input type="hidden" name="Policy" value="<--BASE64_POLICY-->" />
<input type="hidden" name="X-Amz-Signature" value="<--SIGNATURE-->" />
<input type="hidden" name="success_action_redirect" value="<--SUCCESS_REDIRECT-->" />
<input type="file" name="file" />
<input type="submit" value="Upload File" />
</form>

Creating POST Policy and Calculating Signature using PHP

In our PHP code we need previously created user Access Key, Secret Key and several other values such as bucket name and region, using these variables wen can construct a POST policy and calculate AWS Signature (Version 4) which are required in our HTML upload form.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
$access_key         = "iam-user-access-key"; //Access Key
$secret_key         = "iam-user-secret-key"; //Secret Key
$my_bucket          = "mybucket"; //bucket name
$region             = "us-east-1"; //bucket region
$success_redirect   = 'http://'. $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; //URL to which the client is redirected upon success (currently self)
$allowd_file_size   = "1048579"; //1 MB allowed Size

//dates
$short_date         = gmdate('Ymd'); //short date
$iso_date           = gmdate("Ymd\THis\Z"); //iso format date
$expiration_date    = gmdate('Y-m-d\TG:i:s\Z', strtotime('+1 hours')); //policy expiration 1 hour from now

//POST Policy required in order to control what is allowed in the request
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
$policy = utf8_encode(json_encode(array(
                    'expiration' => $expiration_date,  
                    'conditions' => array(
                        array('acl' => 'public-read'),  
                        array('bucket' => $my_bucket),
                        array('success_action_redirect' => $success_redirect),
                        array('starts-with', '$key', ''),
                        array('content-length-range', '1', $allowd_file_size),
                        array('x-amz-credential' => $access_key.'/'.$short_date.'/'.$region.'/s3/aws4_request'),
                        array('x-amz-algorithm' => 'AWS4-HMAC-SHA256'),
                        array('X-amz-date' => $iso_date)
                        ))));

//Signature calculation (AWS Signature Version 4)  
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html 
$kDate = hash_hmac('sha256', $short_date, 'AWS4' . $secret_key, true);
$kRegion = hash_hmac('sha256', $region, $kDate, true);
$kService = hash_hmac('sha256', "s3", $kRegion, true);
$kSigning = hash_hmac('sha256', "aws4_request", $kService, true);
$signature = hash_hmac('sha256', base64_encode($policy), $kSigning);
?>

Policy is Base64 encoded security policy that describes what is permitted in the request. For authenticated requests a policy is required, you can learn more about constructing HTTP POST Policy here.

Signature is the HMAC-SHA256 hash of the security policy using AWS Signature Version 4, and is required if a policy document is included with the request, more about AWS Signature Version 4 here.

Success page

After successful upload, AWS redirects user to success page specified in success_action_redirect form field and policy document, it also attaches bucket, etag and key in the query string, which we can use to quickly generate a link to uploaded object on our S3 bucket.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
//After success redirection from AWS S3
if(isset($_GET["key"]))
{
    $filename = $_GET["key"];
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    if(in_array($ext, array("jpg", "png", "gif", "jpeg"))){
        echo '<hr />Image File Uploaded : <br /><img src="//'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'" style="width:100%;" />';
    }else{
        echo '<hr />File Uploaded : <br /><a href="http://'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'">'.$filename.'</a>';
    }
}
?>

Putting together

All we got to do now is put them together in a single PHP file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php
$access_key         = "iam-user-access-key"; //Access Key
$secret_key         = "iam-user-secret-key"; //Secret Key
$my_bucket          = "mybucket"; //bucket name
$region             = "us-east-1"; //bucket region
$success_redirect   = 'http://'. $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; //URL to which the client is redirected upon success (currently self)
$allowd_file_size   = "1048579"; //1 MB allowed Size

//dates
$short_date         = gmdate('Ymd'); //short date
$iso_date           = gmdate("Ymd\THis\Z"); //iso format date
$expiration_date    = gmdate('Y-m-d\TG:i:s\Z', strtotime('+1 hours')); //policy expiration 1 hour from now

//POST Policy required in order to control what is allowed in the request
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
$policy = utf8_encode(json_encode(array(
                    'expiration' => $expiration_date,  
                    'conditions' => array(
                        array('acl' => 'public-read'),  
                        array('bucket' => $my_bucket),
                        array('success_action_redirect' => $success_redirect),
                        array('starts-with', '$key', ''),
                        array('content-length-range', '1', $allowd_file_size),
                        array('x-amz-credential' => $access_key.'/'.$short_date.'/'.$region.'/s3/aws4_request'),
                        array('x-amz-algorithm' => 'AWS4-HMAC-SHA256'),
                        array('X-amz-date' => $iso_date)
                        ))));

//Signature calculation (AWS Signature Version 4)  
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html 
$kDate = hash_hmac('sha256', $short_date, 'AWS4' . $secret_key, true);
$kRegion = hash_hmac('sha256', $region, $kDate, true);
$kService = hash_hmac('sha256', "s3", $kRegion, true);
$kSigning = hash_hmac('sha256', "aws4_request", $kService, true);
$signature = hash_hmac('sha256', base64_encode($policy), $kSigning);
?>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Aws S3 Direct File Uploader</title>
</head>
<body>
<form action="http://<?= $my_bucket ?>.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="${filename}" />
<input type="hidden" name="acl" value="public-read" />
<input type="hidden" name="X-Amz-Credential" value="<?= $access_key; ?>/<?= $short_date; ?>/<?= $region; ?>/s3/aws4_request" />
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Date" value="<?=$iso_date ; ?>" />
<input type="hidden" name="Policy" value="<?=base64_encode($policy); ?>" />
<input type="hidden" name="X-Amz-Signature" value="<?=$signature ?>" />
<input type="hidden" name="success_action_redirect" value="<?= $success_redirect ?>" />
<input type="file" name="file" />
<input type="submit" value="Upload File" />
</form>
<?php
//After success redirection from AWS S3
if(isset($_GET["key"]))
{
    $filename = $_GET["key"];
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    if(in_array($ext, array("jpg", "png", "gif", "jpeg"))){
        echo '<hr />Image File Uploaded : <br /><img src="//'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'" style="width:100%;" />';
    }else{
        echo '<hr />File Uploaded : <br /><a href="http://'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'">'.$filename.'</a>';
    }
}
?>
</body>
</html>

That’s it! you can copy this code or download and play with your own Aws S3 credentials . You can also check-out the demo page that directly uploads files in my S3 bucket, Good luck!

Download Demo

  1. Hi, I have one question regarding your tutorial, I have trying to upload image my s3 bucket and by using this code I have successfully upload image in my S3 bucket, but can you tell me how can I upload in any folder of S3 Bucket. I have spend my whole day for solving this problem, But I have not get success. Can any one help me?

     Reply
  2. Thank you very mach! I had spend more then 2 hours with amazon documentation and fail request, but with you tutorial it’s starting works after 5 minutes!

     Reply
  3. I tried 4 tutorials unsuccessfully, until I found this one. I poured over the S3 API for hours thinking I must have set up my account wrong, and then I simply copied and pasted this code, filled in my credentials and it worked! You have no idea how much time and headache you just saved me. Thank you for such an easy to follow and efficient tutorial!

     Reply
  4. These tips will really help newbies like me. Thanks for sharing these tips

     Reply
  5. I follow all the above steps, but i get an access denied error.

    1
    AccessDenied

    Access Denied

     Reply
  6. Perfect instruction and i will try it. Thanks

     Reply
  7. Thank you. Nice article and well strutured

     Reply
  8. Just fantastic! congratulations man!

     Reply
  9. I have everything setup properly ( I think ) but when I attempt an upload of a small jpg file I get this error:
    Invalid according to Policy: Policy Condition failed: [“eq”, “$bucket”, “mytests3bucketnamehere”]
    where “mytests3bucketnamehere” is simply my lowercase bucket name that exists in my s3 console, with correct bucket policy settings.

    I have verified my bucket name exists and permissions are set to allow all actions for user. What else should I check?

    Thanks for a great tutorial!

     Reply
  10. Question, In case of multi file upload whether we can use this form’s hidden fields set as array for post value to s3?
    Thank you

     Reply
    • AWS only allows you to upload one file at a time if uploading directly to S3. You can do multi file uploads by setting the file input to “multiple” and looping through each of the files, making mulitple submissions via AJAX. To do this you need to set up CORS on the bucket you want to upload to, otherwise you’ll be denied on the grounds of it being a cross-site script. It can be accomplished, as I’ve just got it working on my own project.

      NOTE: This will only work on HTML5 browsers… I think that’s IE 11+. If you need to support archaeologists using old browsers, you’ll probably need to use either the Javascript SDK or an intermediate server. Alternatively, it might be possible to use AWS Lambda in some way. I’m not sure exactly how this might work (haven’t yet used Lambda), but just thought I’d plant the seed of an idea to build from.

      Good luck.

  11. thank you , saran
    for this amazing tutorial,

     Reply
  12. Hi Saran, thank you for great tutorial. But can you please share screenshot so that i can understand how you created bucket. Actually i am new in AWS.

     Reply
Message Type : Question Comment ?
  • Question : Can include code, jsFiddle, codePen etc using Markdown Syntax.
  • Comment : Short comments and questions.