diff --git a/lib/actions/upload-actions.js b/lib/actions/upload-actions.js
--- a/lib/actions/upload-actions.js
+++ b/lib/actions/upload-actions.js
@@ -1,10 +1,14 @@
 // @flow
 
+import invariant from 'invariant';
+import * as React from 'react';
 import uuid from 'uuid';
 
 import blobService from '../facts/blob-service.js';
 import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js';
 import type { CallKeyserverEndpoint } from '../keyserver-conn/keyserver-conn-types.js';
+import { IdentityClientContext } from '../shared/identity-client-context.js';
+import type { AuthMetadata } from '../shared/identity-client-context.js';
 import type { UploadMultimediaResult, Dimensions } from '../types/media-types';
 import { toBase64URL } from '../utils/base64.js';
 import {
@@ -15,7 +19,10 @@
 import type { CallSingleKeyserverEndpoint } from '../utils/call-single-keyserver-endpoint.js';
 import { getMessageForException } from '../utils/errors.js';
 import { useKeyserverCall } from '../utils/keyserver-call.js';
-import { handleHTTPResponseError } from '../utils/services-utils.js';
+import {
+  handleHTTPResponseError,
+  createDefaultHTTPRequestHeaders,
+} from '../utils/services-utils.js';
 import { type UploadBlob } from '../utils/upload-blob.js';
 
 export type MultimediaUploadCallbacks = Partial<{
@@ -144,13 +151,17 @@
 }) => Promise<BlobServiceUploadResult>;
 
 const blobServiceUpload =
-  (callKeyserverEndpoint: CallKeyserverEndpoint): BlobServiceUploadAction =>
+  (
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+    authMetadata: AuthMetadata,
+  ): BlobServiceUploadAction =>
   async input => {
     const { uploadInput, callbacks, keyserverOrThreadID } = input;
     const { encryptionKey, loop, dimensions, thumbHash, blobInput } =
       uploadInput;
     const blobHolder = uuid.v4();
     const blobHash = toBase64URL(uploadInput.blobHash);
+    const defaultHeaders = createDefaultHTTPRequestHeaders(authMetadata);
 
     // 1. Assign new holder for blob with given blobHash
     let blobAlreadyExists: boolean;
@@ -165,6 +176,7 @@
             blob_hash: blobHash,
           }),
           headers: {
+            ...defaultHeaders,
             'content-type': 'application/json',
           },
         },
@@ -196,6 +208,7 @@
             blobHash,
             blobInput,
           },
+          authMetadata,
           { ...callbacks },
         );
       } catch (e) {
@@ -240,7 +253,25 @@
   };
 
 function useBlobServiceUpload(): BlobServiceUploadAction {
-  return useKeyserverCall(blobServiceUpload);
+  const identityContext = React.useContext(IdentityClientContext);
+  invariant(identityContext, 'Identity context should be set');
+  const { getAuthMetadata } = identityContext;
+
+  const blobUploadAction = React.useCallback(
+    (
+      callSingleKeyserverEndpoint: CallSingleKeyserverEndpoint,
+    ): BlobServiceUploadAction =>
+      async input => {
+        const authMetadata = await getAuthMetadata();
+        const authenticatedUploadAction = blobServiceUpload(
+          callSingleKeyserverEndpoint,
+          authMetadata,
+        );
+        return authenticatedUploadAction(input);
+      },
+    [getAuthMetadata],
+  );
+  return useKeyserverCall(blobUploadAction);
 }
 
 export {
diff --git a/lib/utils/blob-service-upload.js b/lib/utils/blob-service-upload.js
--- a/lib/utils/blob-service-upload.js
+++ b/lib/utils/blob-service-upload.js
@@ -3,10 +3,12 @@
 import invariant from 'invariant';
 import _throttle from 'lodash/throttle.js';
 
+import { createHTTPAuthorizationHeader } from './services-utils.js';
 import type {
   MultimediaUploadCallbacks,
   BlobServiceUploadFile,
 } from '../actions/upload-actions.js';
+import type { AuthMetadata } from '../shared/identity-client-context.js';
 
 function blobServiceUploadHandler(
   url: string,
@@ -15,6 +17,7 @@
     blobHash: string,
     blobInput: BlobServiceUploadFile,
   },
+  authMetadata: AuthMetadata,
   options?: ?MultimediaUploadCallbacks,
 ): Promise<void> {
   if (input.blobInput.type !== 'file') {
@@ -28,6 +31,9 @@
   const xhr = new XMLHttpRequest();
   xhr.open(method, url);
 
+  const authHeader = createHTTPAuthorizationHeader(authMetadata);
+  xhr.setRequestHeader('Authorization', authHeader);
+
   const { timeout, onProgress, abortHandler } = options ?? {};
 
   if (timeout) {
diff --git a/native/utils/blob-service-upload.js b/native/utils/blob-service-upload.js
--- a/native/utils/blob-service-upload.js
+++ b/native/utils/blob-service-upload.js
@@ -6,11 +6,13 @@
 import { pathFromURI } from 'lib/media/file-utils.js';
 import type { BlobServiceUploadHandler } from 'lib/utils/blob-service-upload.js';
 import { getMessageForException } from 'lib/utils/errors.js';
+import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js';
 
 const blobServiceUploadHandler: BlobServiceUploadHandler = async (
   url,
   method,
   input,
+  authMetadata,
   options,
 ) => {
   if (input.blobInput.type !== 'uri') {
@@ -23,6 +25,7 @@
       path = resolvedPath;
     }
   }
+  const headers = authMetadata && createDefaultHTTPRequestHeaders(authMetadata);
   const uploadTask = FileSystem.createUploadTask(
     url,
     path,
@@ -31,6 +34,7 @@
       fieldName: 'blob_data',
       httpMethod: method,
       parameters: { blob_hash: input.blobHash },
+      headers,
     },
     uploadProgress => {
       if (options?.onProgress) {
diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js
--- a/web/input/input-state-container.react.js
+++ b/web/input/input-state-container.react.js
@@ -41,6 +41,8 @@
 import commStaffCommunity from 'lib/facts/comm-staff-community.js';
 import { getNextLocalUploadID } from 'lib/media/media-utils.js';
 import { pendingToRealizedThreadIDsSelector } from 'lib/selectors/thread-selectors.js';
+import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
+import type { IdentityClientContextType } from 'lib/shared/identity-client-context.js';
 import {
   createMediaMessageInfo,
   localIDPrefix,
@@ -95,6 +97,7 @@
 } from 'lib/utils/redux-promise-utils.js';
 import { useDispatch } from 'lib/utils/redux-utils.js';
 import { generateReportID } from 'lib/utils/report-utils.js';
+import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js';
 
 import {
   type BaseInputState,
@@ -151,6 +154,7 @@
   +registerSendCallback: (() => mixed) => void,
   +unregisterSendCallback: (() => mixed) => void,
   +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc<RawTextMessageInfo>,
+  +identityContext: ?IdentityClientContextType,
 };
 type WritableState = {
   pendingUploads: {
@@ -867,6 +871,9 @@
     const steps = [...upload.steps];
     let userTime;
 
+    const { identityContext } = this.props;
+    invariant(identityContext, 'Identity context should be set');
+
     const sendReport = (missionResult: MediaMissionResult) => {
       const newThreadID = this.getRealizedOrPendingThreadID(threadID);
       const latestUpload = this.state.pendingUploads[newThreadID][localID];
@@ -907,6 +914,7 @@
           encryptionKey && blobHash && dimensions,
           'incomplete encrypted upload',
         );
+
         uploadResult = await this.props.blobServiceUpload({
           uploadInput: {
             blobInput: {
@@ -1011,7 +1019,11 @@
     });
 
     if (encryptionKey) {
-      const { steps: preloadSteps } = await preloadMediaResource(result.uri);
+      const authMetadata = await identityContext.getAuthMetadata();
+      const { steps: preloadSteps } = await preloadMediaResource(
+        result.uri,
+        authMetadata,
+      );
       steps.push(...preloadSteps);
     } else {
       const { steps: preloadSteps } = await preloadImage(result.uri);
@@ -1202,6 +1214,8 @@
             keyserverOrThreadID: threadID,
           });
           if (isBlobServiceURI(pendingUpload.uri)) {
+            const identityContext = this.props.identityContext;
+            invariant(identityContext, 'Identity context should be set');
             invariant(
               pendingUpload.blobHolder,
               'blob service upload has no holder',
@@ -1209,16 +1223,22 @@
             const endpoint = blobService.httpEndpoints.DELETE_BLOB;
             const holder = pendingUpload.blobHolder;
             const blobHash = blobHashFromBlobServiceURI(pendingUpload.uri);
-            void fetch(makeBlobServiceEndpointURL(endpoint), {
-              method: endpoint.method,
-              body: JSON.stringify({
-                holder,
-                blob_hash: blobHash,
-              }),
-              headers: {
-                'content-type': 'application/json',
-              },
-            });
+            void (async () => {
+              const authMetadata = await identityContext.getAuthMetadata();
+              const defaultHeaders =
+                createDefaultHTTPRequestHeaders(authMetadata);
+              await fetch(makeBlobServiceEndpointURL(endpoint), {
+                method: endpoint.method,
+                body: JSON.stringify({
+                  holder,
+                  blob_hash: blobHash,
+                }),
+                headers: {
+                  ...defaultHeaders,
+                  'content-type': 'application/json',
+                },
+              });
+            })();
           }
         }
         const newPendingUploads = _omit([localUploadID])(currentPendingUploads);
@@ -1656,6 +1676,7 @@
     const dispatch = useDispatch();
     const dispatchActionPromise = useDispatchActionPromise();
     const modalContext = useModalContext();
+    const identityContext = React.useContext(IdentityClientContext);
 
     const [sendCallbacks, setSendCallbacks] = React.useState<
       $ReadOnlyArray<() => mixed>,
@@ -1696,6 +1717,7 @@
         registerSendCallback={registerSendCallback}
         unregisterSendCallback={unregisterSendCallback}
         textMessageCreationSideEffectsFunc={textMessageCreationSideEffectsFunc}
+        identityContext={identityContext}
       />
     );
   });
diff --git a/web/media/media-utils.js b/web/media/media-utils.js
--- a/web/media/media-utils.js
+++ b/web/media/media-utils.js
@@ -5,13 +5,16 @@
 import { thumbHashToDataURL } from 'thumbhash';
 
 import { fetchableMediaURI } from 'lib/media/media-utils.js';
+import type { AuthMetadata } from 'lib/shared/identity-client-context.js';
 import type {
   MediaType,
   Dimensions,
   MediaMissionStep,
   MediaMissionFailure,
 } from 'lib/types/media-types.js';
+import { isBlobServiceURI } from 'lib/utils/blob-service.js';
 import { getMessageForException } from 'lib/utils/errors.js';
+import { createDefaultHTTPRequestHeaders } from 'lib/utils/services-utils.js';
 
 import { probeFile } from './blob-utils.js';
 import { decryptThumbhashToDataURL } from './encryption-utils.js';
@@ -64,15 +67,23 @@
  * @returns Steps and the result of the preload. The preload is successful
  * if the HTTP response is OK (20x).
  */
-async function preloadMediaResource(uri: string): Promise<{
+async function preloadMediaResource(
+  uri: string,
+  authMetadata: AuthMetadata,
+): Promise<{
   steps: $ReadOnlyArray<MediaMissionStep>,
   result: { +success: boolean },
 }> {
+  let headers;
+  if (isBlobServiceURI(uri)) {
+    headers = createDefaultHTTPRequestHeaders(authMetadata);
+  }
+
   const start = Date.now();
   const mediaURI = fetchableMediaURI(uri);
   let success, exceptionMessage;
   try {
-    const response = await fetch(mediaURI);
+    const response = await fetch(mediaURI, { headers });
     // we need to read the blob to make sure the browser caches it
     await response.blob();
     success = response.ok;