Front-End Web & Mobile

IAM Compute Roles for Server-Side Rendering with AWS Amplify Hosting

Today, AWS Amplify Hosting is introducing compute roles for AWS Amplify applications, enabling you to extend server-side rendering capabilities with secure access to AWS services from the compute runtime. With compute roles, developers can attach specific permissions to their server-side rendered apps, allowing Amplify to make authorized calls to other AWS services. This new capability helps streamline the development process while maintaining security best practices for your applications.

Key capabilities

With compute roles, you can now:

  1. Access sensitive configuration data at runtime within Next.js API routes using AWS Secrets Manager and AWS Systems Manager Parameter Store
  2. Connect your SSR apps directly to databases like Amazon RDS, Amazon DynamoDB. and other AWS databases
  3. Define fine-grained permissions for your compute environment using AWS Identity and Access Management (IAM) policies
  4. Make secure, authenticated calls to any AWS service from your server-side code

Tutorial

To get started, follow these steps to create an IAM compute role and associate it with an Amplify Next.js application:

Prerequisites

Before you begin, make sure you have the following installed:

Create an IAM compute role

Let’s create an IAM role that allows Amplify Hosting’s SSR compute service to securely access AWS resources based on the role’s permission and trust relationship. In our example, we’ll securely access an Amazon Simple Storage Service (Amazon S3) bucket.

  1. Sign in to the AWS Management console and navigate to the IAM console
  2. Under the Access Management tab, choose Roles, then select Create role
  3. Select Custom trust policy and enter the following policy to allow Amplify to assume the role:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "amplify.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
JSON
  1. Add the AWS managed AmazonS3ReadOnlyAccess permission policy to the role and choose Next
  2. Name the role amplify-compute-role and choose Create role

You have successfully created a compute role that Amplify can assume to connect to other AWS services.

Security Best Practice: Implement the principle of least privilege by granting only essential permissions for your specific use case.

Create an Amazon S3 bucket

We will now setup a private Amazon S3 bucket that the Next.js app can securely access using the compute role. Instead of exposing Amazon S3 objects publicly or managing access keys, we’ll use Amplify Hosting’s compute role to securely retrieve private content from Amazon S3.

  1. Sign in to the AWS Management console and navigate to the Amazon S3 console
  2. Choose Create bucket and enter a unique bucket name
  3. Keep ACLs disabled and Block all public access enabled

Object Ownership and Public Access settings for the bucket

  1. Keep the default settings and choose Create bucket
  2. Navigate to the bucket and choose Upload
  3. Upload an image (e.g. amplify-logo.png)

Security Verification: To confirm that your S3 bucket is completely locked down, run the following get-public-access-block AWS CLI command:

// Note: Replace amplify-compute-role-demo with your specific bucket name
aws s3api get-public-access-block --bucket amplify-compute-role-demo
Bash

The expected output should show:

{
    "PublicAccessBlockConfiguration": {
        "BlockPublicAcls": true,
        "IgnorePublicAcls": true,
        "BlockPublicPolicy": true,
        "RestrictPublicBuckets": true
    }
}
JSON

These settings ensure:

  • No public ACLs can be created
  • Existing public ACLs are ignored
  • Public bucket policies are blocked
  • Public access to the bucket is restricted

Create a Next.js app

Next, we will create a Next.js app to securely access private content (an image) from an Amazon S3 bucket using an API route.

  1. Create a new Next.js 15 app with Typescript and Tailwind CSS
npx create-next-app@latest compute-role-demo --typescript --tailwind --eslint
cd compute-role-demo
Bash
  1. Install the AWS SDK for JavaScript S3 Client package:
npm install @aws-sdk/client-s3
Bash
  1. Create an Amazon S3 Image API route:
// app/api/image/route.ts

import { S3Client, GetObjectCommand, S3ServiceException } from "@aws-sdk/client-s3";
import { NextResponse } from 'next/server';

const s3Client = new S3Client({});

const BUCKET_NAME = 'amplify-compute-role-demo';
const IMAGE_KEY = 'amplify-logo.png';

export async function GET() {
  console.log(`[S3 Image Request] Starting - Bucket: ${BUCKET_NAME}, Key: ${IMAGE_KEY}`);
  
  try {
    console.log('[S3 Image Request] Creating GetObjectCommand...');
    const command = new GetObjectCommand({
      Bucket: BUCKET_NAME,
      Key: IMAGE_KEY
    });

    console.log('[S3 Image Request] Sending request to S3...');
    const response = await s3Client.send(command);
    console.log('[S3 Image Request] Received S3 response:', {
      contentType: response.ContentType,
      contentLength: response.ContentLength,
      metadata: response.Metadata
    });

    console.log('[S3 Image Request] Converting response to byte array...');
    const buffer = await response.Body?.transformToByteArray();

    if (!buffer) {
      console.error('[S3 Image Request] No buffer received from S3');
      throw new Error('No image data received from S3');
    }

    console.log('[S3 Image Request] Successfully processed image:', {
      bufferSize: buffer.length,
      contentType: response.ContentType
    });

    return new NextResponse(buffer, {
      headers: {
        'Content-Type': response.ContentType || 'image/png',
        'Cache-Control': 'public, max-age=31536000, immutable'
      }
    });

  } catch (error) {
    if (error instanceof S3ServiceException) {
      console.error('[S3 Image Request] AWS S3 Error:', {
        message: error.message,
        code: error.name,
        requestId: error.$metadata?.requestId,
        statusCode: error.$metadata?.httpStatusCode
      });
    } else {
      console.error('[S3 Image Request] Unexpected error:', error);
    }

    return NextResponse.json({
      success: false,
      error: 'Failed to load content'
    }, { 
      status: 500 
    });
  } finally {
    console.log('[S3 Image Request] Request completed');
  }
}
TypeScript
  1. Create a client component:
// app/components/S3Component.tsx

'use client';

import { useState } from 'react';
import Image from 'next/image';

export default function S3Component() {
  const [imageError, setImageError] = useState(false);
  const [isRevealed, setIsRevealed] = useState(false);

  return (
    <div className="absolute inset-0 top-[88px] flex items-center justify-center">
      <div className="w-full max-w-2xl rounded-2xl mx-4">
        <div className="flex flex-col items-center justify-center min-h-[400px] p-8">
          {!isRevealed ? (
            <button
              onClick={() => setIsRevealed(true)}
              className="px-8 py-4 bg-[rgb(117,81,194)] text-white text-lg rounded-xl 
                       hover:bg-[rgb(107,71,184)] transition-colors"
            >
              <span className="flex items-center gap-2">
                Access Private S3 Bucket
                <span className="text-xl"></span>
              </span>
            </button>
          ) : (
            <div className="space-y-8 w-full">              
              {!imageError && (
                <div className="relative h-64 w-full">
                  <Image 
                    src="/api/image"
                    alt="Amplify Logo"
                    fill
                    unoptimized
                    priority
                    onError={() => setImageError(true)}
                    className="object-contain"
                    sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
                  />
                </div>
              )}
              
              <div className="text-center">
                <p className="text-gray-400 text-sm">
                  This content is securely served from S3 using an IAM Compute Role.
                </p>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}
TypeScript
  1. Update the home page to render the client component:
import S3Component from './components/S3Component';

export default function HomePage() {
  return (
    <div className="relative min-h-screen bg-[rgb(0,0,0)]">
      <h1 className="text-2xl font-bold text-white py-8 text-center relative z-10">
        Amplify Hosting Compute Role Demo
      </h1>
      <S3Component />
    </div>
  );
}
TypeScript
  1. Push the changes to a git repository:
    1. Create a new GitHub repository
    2. Add and commit the changes to the git branch
    3. Add remote origin and push the changes upstream
git add .
git commit -m "initial commit"
git remote add origin https://github.com/<OWNER>/amplify-compute-role-demo.git
git push -u origin main
Git

Deploy the application to Amplify Hosting

  1. Sign in to the AWS Management Console and navigate to the Amplify console
  2. Choose Create new app and select GitHub as the repository source
  3. Authorize Amplify to access your GitHub account
  4. Choose the repository and branch you created
  5. Review the App settings and then choose Next
  6. Review the overall settings and choose Save and deploy

Attaching the IAM compute role to an Amplify app

  1. In the Amplify console, select your app and navigate to App settings > IAM roles
IAM roles section displayed in the Amplify console
  1. Choose Edit in the compute role section

Compute role section displayed in the Amplify console

  1. From the role menu, select the amplify-compute-role and choose Save
  2. You can also add branch overrides to use a unique compute role per branch. This can particularly be helpful across different git environments such as dev, staging, or production.

Access the deployed Next.js app

Navigate to the app’s Overview tab in the Amplify console and open the default Amplify generated URL in the browser.

Overview tab with a Visit deployed URL button in the Amplify console

Next, choose the Access Private S3 button. You should now observe the image that was uploaded to the Amazon S3 bucket.

Review the hosting compute logs

From the App home page, navigate to Hosting > Monitoring and then choose the Hosting compute logs tab.

Monitoring section of the app displayed in the Amplify console

Navigate to the Amazon CloudWatch log streams URL and review the latest log stream. The logs should contain the Amazon S3 image requests:

Hosting compute logs displaying Amazon S3 image requests

Congratulations! You’ve successfully created and attached an IAM compute role to your Next.js SSR application on Amplify Hosting, enabling secure content retrieval from your private Amazon S3 bucket! 🚀

Cleanup

  1. Delete the AWS Amplify app by navigating to App settings > General settings, then choose Delete app.
  2. Delete the Amazon S3 bucket by navigating to S3 console > Select bucket > Select Delete > Enter the bucket name to confirm deletion
  3. Delete the IAM role by navigating to IAM console > Select Roles > Find the amplify-compute-role name > Select Delete > Enter the role name to confirm deletion

Summary

AWS Amplify Hosting now offers compute roles for server-side rendered (SSR) applications, solving the challenge of securely accessing AWS services. Previously, developers manually managed credentials through environment variables. Now, with compute roles, developers can directly attach IAM permissions to their SSR apps, simplifying service integration and enhancing security.

Add compute roles to your SSR apps today by checking out the Amplify documentation.

About the Authors

Photo of author

Jay Raval

Jay Raval is a Solutions Architect on the AWS Amplify team. He’s passionate about solving complex customer problems in the front-end, web and mobile domain and addresses real-world architecture problems for development using front-end technologies and AWS. In his free time, he enjoys traveling and sports. You can find Jay on X @_Jay_Raval_

Photo of author

Matt Auerbach

Matt Auerbach is a NYC-based Product Manager on the AWS Amplify Team. He educates developers regarding products and offerings, and acts as the primary point of contact for assistance and feedback. Matt is a mild-mannered programmer who enjoys using technology to solve problems and making people’s lives easier. B night, however…well he does pretty much the same thing. You can find Matt on X @mauerbac. He previously worked at Twitch, Optimizely & Twilio.