Skip to main content
Version: MuddVision

S3 Profile Image Upload

Author(s)

  • Dipak Mourya
  • Yeasa Mondal

Last Updated Date

2026-04-14


SRS References


Version History

VersionDateChangesAuthor
1.02026-04-14Initial draftDipak Mourya, Yeasa Mondal

Feature Overview

Objective:
Allow users to upload and update their profile picture from the frontend profile section. The image is uploaded directly to AWS S3 via a presigned URL generated by the backend, minimizing server load. Once uploaded, the backend validates the image in S3 and persists the profile picture URL to the database.

Scope:

  • Frontend profile section UI trigger for image selection and upload
  • Backend API to generate a presigned S3 upload URL
  • Direct browser-to-S3 upload using the presigned URL
  • Backend confirmation API to validate the upload and persist the profile URL
  • Frontend state update to reflect the new profile image

Dependencies:

  • AWS S3: Storage for profile images
  • AWS Presigned URL: Secure, time-limited direct upload mechanism
  • Auth Service (Backend): Generates presigned URLs and confirms uploads
  • Frontend (React/TypeScript): Profile section UI and HTTP service layer

UI Screens

Profile Page — Avatar with Edit Trigger

The Profile page displays the user's current profile picture (or initials avatar if no image is set). A pencil icon overlaid on the avatar acts as the entry point for updating the profile image.

Profile page showing avatar with pencil edit icon

UI Elements:

ElementDescription
Avatar / InitialsCircular avatar showing the user's profile picture or initials (e.g. "AD")
Pencil (Edit) IconSmall edit button overlaid on the bottom-right of the avatar; opens the image editor modal when clicked
User NameDisplayed below the avatar
Personal Info tabActive tab showing user overview (Email, Phone, Role, Status, Joined date)

Behaviour:

  • If no profile picture has been uploaded, the avatar shows the user's initials.
  • Clicking the pencil icon opens a file picker. Once a file is selected, the Edit & Adjust Profile Image modal is shown.

Edit & Adjust Profile Image Modal

After selecting an image file, the user is presented with a modal to crop, zoom, and rotate the image before saving.

Edit and Adjust Profile Image modal with Zoom and Rotate sliders

UI Elements:

ElementDescription
Image PreviewCircular crop preview of the selected image
Zoom SliderAllows the user to zoom in/out on the image within the crop area
Rotate SliderAllows the user to rotate the image within the crop area
Cancel ButtonDismisses the modal without uploading; no API calls are made
Save ButtonTriggers the three-step upload flow (presigned URL → S3 PUT → confirm)

Behaviour:

  • Adjustments (zoom, rotate) are applied client-side before the image Blob is sent.
  • Clicking Save initiates the updateProfileImage function with the cropped/adjusted Blob.
  • On success, the avatar on the Profile page is updated to display the new image.
  • On failure, an error message is shown and the avatar remains unchanged.

API Interfaces

EndpointMethodRequest BodyResponseStatus Codes
/api/auth/user/picture/upload-urlPOST{ "fileName": "string", "contentType": "string" }{ "photoKey": "string", "uploadUrl": "string", "expiresAtUtc": "ISO8601 datetime" }200 OK, 400, 401, 500
AWS S3 Presigned URL (external)PUTRaw image binary (Blob) with Content-Type headerEmpty body on success200 OK, 403 Forbidden
/api/auth/user/picture/uploadedPOST{ "photoKey": "string" }{ "message": "string" } / 204 No Content200 OK, 204, 400, 401, 500

Implementation Details

Upload Flow Overview

The profile image upload follows a three-step presigned URL pattern to upload images directly to S3 without routing the binary data through the backend server.

Frontend (Profile Section)

│ 1. User selects image

POST /api/auth/user/picture/upload-url
{ fileName, contentType }

│ 2. Backend generates presigned S3 URL

Response: { photoKey, uploadUrl, expiresAtUtc }

│ 3. Browser PUT to AWS presigned URL
│ Headers: Content-Type: image/jpeg (or actual type)
│ Body: raw image Blob

AWS S3 (direct upload)

│ 4. Confirm upload with backend

POST /api/auth/user/picture/uploaded
{ photoKey }

│ 5. Backend validates object in S3,
│ saves photokey in the cognito default attribute 'picture' and in the Mudd db tblMudd_user photo_url column,
│ returns updated profile info

Frontend updates profile image in UI state

Resolve Profile Image URL on Fetch

When the frontend fetches Profile data or the User List, the backend reads the stored photoKey from the database (for example, from the Mudd user table and Cognito picture attribute mapping).

Using that photoKey, the backend resolves the image URL from S3 ( short-lived read URL as per environment configuration) and includes that URL in the API response.

The frontend uses this returned image URL directly to display the user profile picture in avatar and list views.


Previous Image Cleanup

As part of the complete upload confirmation API flow, the backend first checks whether the user already has a previously saved profile image.

If an old image exists, the backend reads the previous photoKey and deletes that old object from S3 before completing the update with the new photoKey.

Also there is a delete picture api that can be used to delete the profile picture(delete object from s3, and photo key from cognito and mudd db).


Step 1 — Get Presigned Upload URL

The frontend calls the backend to obtain a time-limited presigned URL and the photoKey that identifies the object in S3.

Request

POST /api/auth/user/picture/upload-url
Content-Type: application/json
Authorization: Bearer <token>

{
"fileName": "profile.jpg",
"contentType": "image/jpeg"
}

Response

{
"photoKey": "users/abc123/profile.jpg",
"uploadUrl": "https://s3.amazonaws.com/bucket/users/abc123/profile.jpg?X-Amz-Signature=...",
"expiresAtUtc": "2026-04-14T07:26:54.954Z"
}

Step 2 — Upload Image Directly to S3

Using the uploadUrl returned in Step 1, the frontend performs a PUT request directly to AWS S3. No auth headers are needed; the presigned URL carries the authorization.

PUT <uploadUrl>
Content-Type: image/jpeg

<raw image binary>

Step 3 — Confirm Upload with Backend

After a successful S3 upload, the frontend notifies the backend using the photoKey. The backend verifies the object exists in S3 and is valid, then saves the photokey to the cognito and mudd database.

Request

POST /api/auth/user/picture/uploaded
Content-Type: application/json
Authorization: Bearer <token>

{
"photoKey": "users/abc123/profile.jpg"
}

Response

{
"message": "Profile picture updated successfully."
}

Backend Responsibilities

Generate Presigned URL (/api/auth/user/picture/upload-url)

  1. Authenticate the request via JWT token.
  2. Generate a unique photoKey (e.g., users/{userId}/profile_{timestamp}.jpg).
  3. Use the AWS SDK to create a presigned PUT URL for the S3 bucket, scoped to the generated photoKey and the provided contentType.
  4. Return photoKey, uploadUrl, and expiresAtUtc.

Confirm Upload (/api/auth/user/picture/uploaded)

  1. Authenticate the request via JWT token.
  2. Use the photoKey to check that the object exists in S3 (e.g., via HeadObject).
  3. Validate the object metadata (size, content-type) as needed.
  4. Construct the full S3 public/CDN URL for the photoKey.
  5. Persist the new profile picture URL to the user record in the database.
  6. Return success response.

Error Handling

ScenarioBehaviour
Presigned URL request failsError thrown with server message; upload flow aborted
S3 PUT upload fails"Failed to upload image to storage" error thrown
Confirmation API returns errorError thrown with server message; profile URL not persisted
Presigned URL expires before uploadS3 returns 403 Forbidden; user should retry the upload

Security Considerations

  • Presigned URLs are short-lived (expiresAtUtc) and scoped to a single S3 key and HTTP method (PUT).
  • The photoKey is server-generated and tied to the authenticated user, preventing path traversal or overwriting other users' images.
  • JWT authentication is required on both backend endpoints; S3 is not publicly writable.
  • Backend validates the S3 object exists before persisting the URL to the database, preventing phantom/broken profile pictures.