title: How to upload a file to S3 using presigned Url with React.js
body: <p>I'm generating S3 presigned url for uploading the file from local. On frontend I'm using React.  </p>
  
  <p>I get the presigned URL using API call and then trying to upload the file using axios but it gives 403 (Forbidden).  </p>
  
  <p>If I use the same presigned url using 'curl' then it works fine and the same file is uploaded on S3.  </p>
  
  <p>s3.py - For generating the pre-signed url:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>Component in react for uploading the file:</p>
  
  <pre><code>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) =&gt; {
          const fileInfo = event.target.files[0];
          this.setState({audioSelected: fileInfo});
      }
  
      onUpload = async () =&gt; {
          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 =&gt; {
                  this.setState({
                      audioLoaded: (ProgressEvent.loaded / ProgressEvent.total*100)
                  })
              }
          })
          .then(res =&gt; {
              console.log(res);
          })
  
      }
  
      render() {
          return (
              &lt;Fragment&gt;
                  &lt;AudioUploadButton onSelect={this.onSelect} onUpload={this.onUpload} audioSelected={this.state.audioSelected} audioLoaded={this.state.audioLoaded} /&gt;
              &lt;/Fragment&gt;
          )
      }
  }
  
  UpdateAudio = withRouter(UpdateAudio)
  
  export default withApollo(UpdateAudio);
  </code></pre>
  
  <p>AudioUploadButton.js</p>
  
  <pre><code>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 =&gt; ({
  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 (
              &lt;Grid container spacing={8} &gt;
                  &lt;Grid item md={2} xs={12}&gt;
                      &lt;input
                          accept="audio/*"
                          className={classes.input}
                          id="contained-button-file"
                          type="file"
                          onChange = {this.props.onSelect}
                      /&gt;
                      &lt;label htmlFor="contained-button-file"&gt;
                          &lt;Button variant="contained" component="span" className={classes.button}&gt;Select&lt;/Button&gt;
                      &lt;/label&gt;
                  &lt;/Grid&gt;
                  &lt;Grid item md={1} xs={12}&gt;
                      &lt;Fab color="secondary" size='medium' onClick={this.props.onUpload}&gt;
                          &lt;CloudUploadIcon /&gt;
                      &lt;/Fab&gt;
                  &lt;/Grid&gt;
  
                  &lt;Grid item md={9} xs={12}&gt;
                      &lt;Typography variant='caption' gutterBottom&gt;{name} {size} {loaded}&lt;/Typography&gt;
                  &lt;/Grid&gt;
              &lt;/Grid&gt;
          )
      }
  }
  
  
  export default withStyles(styles)(AudioUploadButton);
  </code></pre>
  
  <p>Curl works without any issue:<br>
      curl -X PUT --upload-file 1.jpg <a href="https://s3.amazonaws.com/bucket-name/filepath.jpg?AWSAccessKeyId=xyz&amp;Signature=Vql3Bnkb7H847Cr4vtw5gbi%2F%2Bs%3D&amp;Expires=1546873244" rel="noreferrer">https://s3.amazonaws.com/bucket-name/filepath.jpg?AWSAccessKeyId=xyz&amp;Signature=Vql3Bnkb7H847Cr4vtw5gbi%2F%2Bs%3D&amp;Expires=1546873244</a></p>
  
  <p>Thanks for the help.</p>