How to Upload Images using Amazon S3 and Laravel

February 4th, 2018

Need to upload images to your S3 instance using Laravel? Luckily Laravel makes it dead simple to setup a connection and start uploading to the cloud. But what does it require on the Amazon end? Strap on your polos and lets get AWS certified. 👌

I'm going to presume you have very basic knowledge of AWS, have an active account with Amazon. If you haven't already, I'd recommend signing up for their 1 year free trial, it's a crazy deal.

The AWS Way

We're going to create an S3 bucket, add the proper policies, and associate an IAM user to it. We'll use Laravel to connect to the S3 bucket through the IAM user's credentials (which is why they need proper permission aka policies).

Bucket Challenge

The first step is to sign in to your AWS account as the root user. Then navigate yourself over to the S3 section of the website.

  1. Click the blue "+ Create Bucket" button.
  2. Enter a unique slug for your S3 bucket, select a region for the server, and click next.
  3. Leave the default settings here, make sure "Manage Public Permissions" selects: "Do not grant public read access..". Click next!
  4. Click review.

Congrats, you have a new S3 bucket. Now let's assign an IAM user to the bucket.

Time for IAM

We've got to create a new user (or you can use an existing one), and add
a bucket policy allowing your IAM user to upload to the S3

  1. Create a new user and save their access id and secret.
  2. Apply a new policy to the user below, replacing "my-bucket" with your bucket slug from earlier:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::my-bucket"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl", 
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::my-bucket/*"
            ]
        }
    ]
}

If you still get an 'access denied' when viewing files, try adding the following snippet to your policies from Amazon's FAQ:

{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Sid":"AddPerm",
      "Effect":"Allow",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::examplebucket/*"]
    }
  ]
}

Connect with Laravel

Now that we've been able to create a bucket and assign a user to it, let's add the credentials to Laravel so it can connect!

  1. Open your .ENV file and add the following variables if they don't exist. Make sure to add in your credentials, bucket name, and the bucket's region code:
AWS_ACCESS_KEY_ID=your_key_here
AWS_SECRET_ACCESS_KEY=your_secret_here
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=my-bucket
  1. Go into your Filesystem config file (/laravel-project/config/filesystems.php) and make sure you're using environment variables. The disks array's S3 array should look like this:
    's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
        ],

That's it! You've connected Laravel to your S3 bucket. Easy right?

Flex My S3

Let's test to see if everything worked! You can see the official Laravel docs for more info on using Filesystems, but you can run a quick test by using the following snippet in a controller:

$my_file = 'file.txt';
$handle = fopen($my_file, 'w') or die('Cannot open file:  '.$my_file);
$data = 'Test data to see if this works!';
fwrite($handle, $data);

$storagePath = Storage::disk('s3')->put("uploads", $my_file, 'public');

We create a text file, insert text into it, and upload that text file to the S3 storage. There's also a 3rd parameter on the put method defined as public, which makes the file publicly accessible. You can remove this to make the file private.

The path to the file on S3 is storage in the $storagePath variable, so you can store that in your DB and find posts by using Storage::disk('s3')->get($storagePath).

All in a days bucket

Laravel makes it effortless to use storage systems like S3, but it's services like AWS that complicate the process with complex policies. I hope this saved you 30 minutes or more of writing and researching IAM policies in the poorly written docs.

Stay regular,
Oscar


Keep Reading: