I'm generating S3 presigned url for uploading the file from local. On frontend I'm using React.
I get the presigned URL using API call and then trying to upload the file using axios but it gives 403 (Forbidden).
If I use the same presigned url using 'curl' then it works fine and the same file is uploaded on S3.
s3.py - For generating the pre-signed url:
class S3Controller:
def __init__(self, client=None, bucket=None):
self.client = client
self.bucket = bucket
def signed_url(self, filename):
filename = filename.replace('/', '-').replace(' ', '-')
date = datetime.now()
key = f"audio/{date.year}/{date.month}/{date.day}/{filename}"
url = self.client.generate_presigned_url(
ClientMethod='put_object',
ExpiresIn=3600,
Params={
'Bucket': self.bucket,
'Key': key,
}
)
return url
Component in react for uploading the file:
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import { S3SignedUrl } from '../query';
import { withApollo } from 'react-apollo';
import AudioUploadButton from '../components/AudioUploadButton';
import axios from 'axios';
class UpdateAudio extends Component {
constructor(props) {
super(props);
this.site = "5d517862-0630-431c-94b1-bf34de6bfd8b"
this.state = {
audioSelected: {},
audioLoaded: 0
}
this.onSelect = this.onSelect.bind(this);
this.onUpload = this.onUpload.bind(this);
}
onSelect = (event) => {
const fileInfo = event.target.files[0];
this.setState({audioSelected: fileInfo});
}
onUpload = async () => {
let resp = await this.props.client.query({ query: S3SignedUrl, variables: {filename: this.state.audioSelected.name}});
let { data } = resp;
let endpoint = data.s3SignedUrl.url;
axios.put(endpoint, this.state.audioSelected, {
onUploadProgress: ProgressEvent => {
this.setState({
audioLoaded: (ProgressEvent.loaded / ProgressEvent.total*100)
})
}
})
.then(res => {
console.log(res);
})
}
render() {
return (
<Fragment>
<AudioUploadButton onSelect={this.onSelect} onUpload={this.onUpload} audioSelected={this.state.audioSelected} audioLoaded={this.state.audioLoaded} />
</Fragment>
)
}
}
UpdateAudio = withRouter(UpdateAudio)
export default withApollo(UpdateAudio);
AudioUploadButton.js
import React from 'react';
import { Grid, Button, Typography, Fab } from '@material-ui/core';
import { withStyles } from '@material-ui/core/styles';
import CloudUploadIcon from '@material-ui/icons/CloudUpload';
const styles = theme => ({
button: {
margin: theme.spacing.unit,
},
input: {
display: 'none',
},
fab: {
margin: theme.spacing.unit,
},
});
class AudioUploadButton extends React.Component {
render() {
let { classes } = this.props;
let { name, size } = this.props.audioSelected;
let loaded = this.props.audioLoaded;
return (
<Grid container spacing={8} >
<Grid item md={2} xs={12}>
<input
accept="audio/*"
className={classes.input}
id="contained-button-file"
type="file"
onChange = {this.props.onSelect}
/>
<label htmlFor="contained-button-file">
<Button variant="contained" component="span" className={classes.button}>Select</Button>
</label>
</Grid>
<Grid item md={1} xs={12}>
<Fab color="secondary" size='medium' onClick={this.props.onUpload}>
<CloudUploadIcon />
</Fab>
</Grid>
<Grid item md={9} xs={12}>
<Typography variant='caption' gutterBottom>{name} {size} {loaded}</Typography>
</Grid>
</Grid>
)
}
}
export default withStyles(styles)(AudioUploadButton);
Curl works without any issue:
curl -X PUT --upload-file 1.jpg https://s3.amazonaws.com/bucket-name/filepath.jpg?AWSAccessKeyId=xyz&Signature=Vql3Bnkb7H847Cr4vtw5gbi%2F%2Bs%3D&Expires=1546873244
Thanks for the help.