hassan / laravel-s3-browser-based-uploads
Upload files to AWS S3 Directly from Browser
Installs: 13 450
Dependents: 0
Suggesters: 0
Security: 0
Stars: 4
Watchers: 0
Forks: 2
Open Issues: 0
pkg:composer/hassan/laravel-s3-browser-based-uploads
Requires
- php: ^8.1|^8.2|^8.3
- aws/aws-sdk-php: ^3.0
- graham-campbell/manager: ^4.7|^5.0
- illuminate/contracts: ^9.0|^10.0|^11.0
- illuminate/support: ^9.0|^10.0|^11.0
- league/flysystem-aws-s3-v3: ^3.0
Requires (Dev)
- graham-campbell/testbench: ^5.7
- mockery/mockery: ^1.5
- phpunit/phpunit: ^9.6|^10.0
README
Upload files to AWS S3 directly from the browser using presigned POST requests, reducing server load and bandwidth usage.
Requirements
- PHP 8.1 or higher
- Laravel 9.x, 10.x, or 11.x
- AWS S3 bucket with appropriate permissions
Installation
1. Install the package via composer
composer require hassan/laravel-s3-browser-based-uploads
For Laravel 9+, you may need to install Flysystem dependencies:
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
2. Publish the config file
php artisan vendor:publish --provider="Hassan\S3BrowserBasedUploads\ServiceProvider" --tag=config
3. Configure your AWS credentials
Add your AWS settings to .env:
AWS_ACCESS_KEY_ID=your-access-key-id AWS_SECRET_ACCESS_KEY=your-secret-access-key AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET=your-bucket-name
4. Configure S3 CORS (Required!)
For browser uploads to work, you must configure CORS on your S3 bucket. Add this CORS configuration in your AWS S3 Console:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["POST"],
"AllowedOrigins": ["https://yourdomain.com"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
Important: Replace https://yourdomain.com with your actual domain(s). For local development, you may add http://localhost:8000 or use ["*"] (not recommended for production).
Usage
Basic Usage
use Hassan\S3BrowserBasedUploads\Facades\S3BrowserBasedUploads; // Get the S3 endpoint URL $endpointUrl = S3BrowserBasedUploads::getEndpointUrl(); // Get the presigned POST fields $fields = S3BrowserBasedUploads::getFields(); // Use a different connection $fields = S3BrowserBasedUploads::connection('secure_images')->getFields();
Example
const formData = new FormData(); @foreach(S3BrowserBasedUploads::getFields() as $key => $value) formData.append('{{ $key }}', '{{ $value }}'); @endforeach formData.append('Content-Type', file.type); formData.append('file', file, file.name); const request = new XMLHttpRequest(); request.open('POST', "{{ S3BrowserBasedUploads::getEndpointUrl() }}"); request.send(formData);
Check out the demo with Filepond
Using Credentials Routes
You can optionally register a route that returns the credentials as JSON:
// In your RouteServiceProvider or routes/web.php use Hassan\S3BrowserBasedUploads\S3BrowserBasedUploads; public function boot() { // Registers GET route: /s3_browser_based_uploads/credentials S3BrowserBasedUploads::routes(); // With custom options (e.g., authentication middleware) S3BrowserBasedUploads::routes([ 'middleware' => ['auth', 'throttle:60,1'], 'prefix' => 'api/uploads', ]); }
This creates an endpoint that returns:
{
"url": "https://your-bucket.s3.amazonaws.com",
"fields": {
"key": "tmp/images/${filename}",
"policy": "eyJ...",
"x-amz-algorithm": "AWS4-HMAC-SHA256",
"x-amz-credential": "...",
"x-amz-date": "...",
"x-amz-signature": "..."
}
}
Security Considerations
⚠️ Important Security Warnings
-
Filename Sanitization: Using
${filename}in your config can expose you to path traversal attacks. Consider:// In your backend before generating credentials 'key' => 'uploads/' . Str::uuid() . '.' . $extension
-
File Size Limits: Always set
content-length-rangein your config to prevent abuse:['content-length-range', 1, 10485760] // 1 byte to 10MB
-
Content-Type Validation: Restrict file types using conditions:
['starts-with', '$Content-Type', 'image/'] // Images only ['eq', '$Content-Type', 'application/pdf'] // PDFs only
-
Short Expiration Times: Use short-lived URLs (1-15 minutes recommended):
'expiration_time' => '+5 minutes'
-
Rate Limiting: The credentials endpoint includes default rate limiting (60 requests/minute). Adjust as needed.
-
HTTPS Only: Always use HTTPS in production to prevent credential interception.
-
Bucket Permissions: Set appropriate S3 bucket policies and ACLs. Avoid public write access.
AWS IAM Permissions
Your AWS IAM user needs these S3 permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
Known Limitations
- Does not work with AWS IAM Identity Center credentials (use standard IAM credentials)
- Maximum expiration time is capped at 12 hours for security
- Requires CORS configuration on S3 bucket
Security Disclosure
If you discover any security related issues, please email hello@hassan-ali.me instead of using the issue tracker.