A Step-by-Step Guide to Pre-signing AWS S3 URLs and retrieving protected S3 images in React Native
Introduction
In modern applications, especially those dealing with multimedia content, efficient handling of assets like images is crucial for a smooth user experience. Amazon Simple Storage Service (S3) is a popular choice.
One common approach to secure asset access is using pre-signed URLs. These URLs grant temporary access to specific resources in an S3 bucket, allowing controlled access without compromising security.
While it’s common for pre-signed URLs to be generated on the backend and provided to the frontend, there are scenarios where handling pre-signed URLs directly in the frontend of a React Native app becomes necessary. This could be due to various reasons such as dynamic content, user-specific access controls, or the need for fine-grained access management.
One common approach to secure asset access is using pre-signed URLs. These URLs grant temporary access to specific resources in an S3 bucket, allowing controlled access without compromising security. In this guide, we’ll walk through the process of pre-signing AWS S3 URLs in a React Native application step by step.
While the AWS documentation provides comprehensive guidance on pre-signed URLs, it may not offer specific instructions for integrating this functionality into a React Native application. https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html
Let’s delve into how to do it in our react native app
Prerequisites
- AWS Account: You need an AWS account to access Amazon S3 services. If you don’t have one, you can sign up for an AWS account here.
- Access Key and Secret Key: Access keys and secret keys are essential for authenticating requests to AWS services, including S3. You can obtain these credentials by creating an IAM user with appropriate permissions in the AWS Management Console. Ensure that you store these credentials securely and avoid exposing them in public repositories or client-side code.
- Node.js: Node.js is required for installing packages and running JavaScript-based tools. Make sure you have Node.js installed on your development machine. You can download and install Node.js from the official website: Node.js Downloads.
- React Native Setup: Set up a React Native project environment on your machine if you haven’t already. You can follow the official React Native documentation for instructions on setting up your development environment: React Native — Getting Started.
Step 1: Set Up AWS SDK
First, install the aws4-react-native
package, which provides the necessary tools for signing AWS requests in React Native applications. and react-native-fast-image Additionally, include the installation of the react-native-fast-image
library, which provides a performant and highly customizable image component for React Native applications. This library is used for rendering images efficiently and supporting advanced features such as caching and placeholder rendering.
npm install aws4-react-native
npm install react-native-fast-image
Step 2: Write the Pre-signing Function
Create a function that generates a pre-signed URL for a given S3 object. This function will utilize the aws4
library to sign the request.
let’s imagine our images come with the following URLS:
'https://s3.bucket-name.com/compressed-images/your-image-key.webp';
Where:
"s3.bucket-name.com" is the S3 bucket hostname
"compressed-images/your-image-key.webp" The rest of the URL is the image path
"your-image-key.webp" is the image key
// helpers/generate-presigned-urls.ts
// Import the necessary modules
var aws4 = require('aws4-react-native');
// Function to generate a pre-signed URL
export function generatePreSignedUrl(imageUrl, expirationSeconds = 3600) {
// Extract the object key from the image URL
const parts = imageUrl.split('/');
const lastPart = parts[parts.length - 1];
const path = 'compressed-images/' + lastPart;
// Define options for signing
const opts = {
service: 's3',
region: 'your-region', // eu-west-2 for example
path: path,
host: 'your-s3-host', // s3.bucket-name.com in our case
};
// Sign the request
aws4.sign(opts, {
accessKeyId: 'ACCESS_KEY',
secretAccessKey:' SECRET_KEY',
});
// Return the signed headers
return {
Authorization: opts.headers.Authorization,
X_Amz_Date: opts.headers['X-Amz-Date'],
};
}
The result will be something like that
const imageUrl = 'https://s3.bucket-name.com/compressed-images/your-image-key.webp';
const presignedUrl = generatePreSignedUrl(imageUrl)
console.log("Pre-signed URL:",presignedUrl)
Pre-signed URL: https://s3.REGION.amazonaws.com/s3.bucket-name.com/compressed-images/your-image-key.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ACCESS_KEYaws4_request&X-Amz-Date=20231018T075941Z&X-Amz-Expires=3600&X-Amz-Signature=SIGNATURE-SignedHeaders=host
Step 3: Create the Image Component
import React from 'react';
import { ActivityIndicator, ImageStyle, StyleProp, StyleSheet, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import { generatePreSignedUrl } from 'helpers/generate-presigned-url'; // the earlier created function for generating presigned URLs
interface Props {
imageURL: string;
style: StyleProp<ImageStyle>;
resizeMode: 'contain' | 'cover' | 'stretch';
}
export const AWSS3Image = React.memo((props: Props) => {
const [isLoading, setIsLoading] = React.useState(true);
const presignedURL = React.useMemo(() => {
return generatePreSignedUrl(props.imageURL);
}, [props.imageURL]);
const onLoading = () => {
setIsLoading(false);
};
return (
<FastImage
source={{
uri: props.imageURL,
headers: {
'X-Amz-Content-Sha256':
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', // hash value of an empty strin, remains the same for all implementations
'X-Amz-Date': presignedURL.X_Amz_Date,
Authorization: presignedURL.Authorization,
},
}}
style={props.style}
resizeMode={props.resizeMode}
onLoad={onLoading}>
{isLoading && (
<View style={styles.loaderContainer}>
<ActivityIndicator size="large" />
</View>
)}
</FastImage>
);
});
const styles = StyleSheet.create({
loaderContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
The 'X-Amz-Content-Sha256'
header with the value 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
is related to the SHA-256 hash of an empty string. This hash value represents the content of the request payload, which, in the context of generating pre-signed URLs for AWS S3, is often an empty string. This value remains constant across implementations and is essential for maintaining request integrity and ensuring successful authentication with AWS S3.
Step 4: Usage
You can now use the ImageFromAWSS3
component in your React Native application to display images from AWS S3 securely.
import React from 'react';
import { View, ImageStyle, StyleProp } from 'react-native';
import { AWSS3Image } from './path/to/aws-s3-image';
const App = () => {
return (
<View>
<AWSS3Image
imageURL="https://your-s3-bucket-url/image.jpg"
style={styles.image}
resizeMode="cover"
/>
</View>
);
};
const styles = {
image: {
width: 200,
height: 200,
} as StyleProp<ImageStyle>,
};
export default App;
Conclusion
In summary, this article provided a practical guide on implementing pre-signed URLs in React Native applications to access protected assets stored in AWS S3. We covered the process of generating pre-signed URLs programmatically using AWS SDKs, along with the integration of the react-native-fast-image
library for efficient image rendering. By following these steps, developers can securely grant temporary access to specific S3 resources while ensuring optimal performance in React Native applications.