Skip to main content
Secure uploads ensure that only authorized users can upload files to your Uploadcare project by requiring cryptographically signed requests. This prevents unauthorized uploads and protects your account from abuse.

Why Use Secure Uploads?

Without secure uploads, anyone with your public key can upload files to your project. Secure uploads add an authentication layer that:
  • Prevents unauthorized uploads from malicious users
  • Protects against API abuse and quota exhaustion
  • Ensures uploads originate from your application
  • Provides granular control over upload permissions

Configuration Options

There are three ways to configure secure uploads:

1. Static Signature (Simple)

Provide a pre-generated signature and expiration time:
<uc-config 
  pubkey="YOUR_PUBLIC_KEY"
  secure-signature="YOUR_SIGNATURE"
  secure-expire="1234567890">
</uc-config>
Limitations:
  • Signature expires at the specified time
  • Must be regenerated manually after expiration
  • Less secure for long-running applications
Use a resolver function to fetch signatures on-demand:
<uc-config id="config" pubkey="YOUR_PUBLIC_KEY"></uc-config>

<script>
  const config = document.getElementById('config');
  
  config.secureUploadsSignatureResolver = async () => {
    const response = await fetch('/api/uploadcare-signature');
    const data = await response.json();
    
    return {
      secureSignature: data.signature,
      secureExpire: data.expire,
    };
  };
</script>

3. Combined Approach

Provide a static signature initially and use a resolver for renewal:
config.secureSignature = 'initial_signature';
config.secureExpire = '1234567890';
config.secureUploadsSignatureResolver = async () => {
  // Fetches new signature when needed
  const response = await fetch('/api/uploadcare-signature');
  return await response.json();
};
If both static and resolver are provided, the resolver takes precedence. A warning will be logged to the console.

Secure Uploads Manager

The SecureUploadsManager handles signature management automatically:
// Source: src/abstract/managers/SecureUploadsManager.ts
export class SecureUploadsManager extends SharedInstance {
  private _secureToken: SecureUploadsSignatureAndExpire | null = null;

  public async getSecureToken(): Promise<SecureUploadsSignatureAndExpire | null> {
    const { 
      secureSignature, 
      secureExpire, 
      secureUploadsSignatureResolver, 
      secureUploadsExpireThreshold 
    } = this._cfg;

    // Warn if both are provided
    if ((secureSignature || secureExpire) && secureUploadsSignatureResolver) {
      console.warn(
        'Both secureSignature/secureExpire and secureUploadsSignatureResolver are set. ' +
        'secureUploadsSignatureResolver will be used.'
      );
    }

    // Use resolver if available
    if (secureUploadsSignatureResolver) {
      // Check if token is expired
      if (!this._secureToken || 
          isSecureTokenExpired(this._secureToken, { threshold: secureUploadsExpireThreshold })) {
        
        try {
          const result = await secureUploadsSignatureResolver();
          
          if (!result) {
            this._debugPrint('Secure signature resolver returned nothing.');
            this._secureToken = null;
          } else if (!result.secureSignature || !result.secureExpire) {
            console.error('Secure signature resolver returned an invalid result:', result);
          } else {
            this._debugPrint('Secure signature resolved:', result);
            this._secureToken = result;
          }
        } catch (err) {
          console.error('Secure signature resolving failed.', err);
        }
      }

      return this._secureToken;
    }

    // Fall back to static signature
    if (secureSignature && secureExpire) {
      return { secureSignature, secureExpire };
    }

    return null;
  }
}

Expiration Handling

The library checks if a signature is expired using a configurable threshold:
// Source: src/utils/isSecureTokenExpired.ts
export const isSecureTokenExpired = (
  secureToken: SecureUploadsSignatureAndExpire,
  { threshold }: { threshold?: number },
): boolean => {
  const { secureExpire } = secureToken;
  const nowUnix = Math.floor(Date.now() / 1000);
  const expireUnix = Number(secureExpire);
  const thresholdUnix = Math.floor((threshold || 0) / 1000);
  
  return nowUnix + thresholdUnix >= expireUnix;
};

Expiration Threshold

The threshold determines how early to refresh the signature before it actually expires:
<!-- Refresh signature 60 seconds before expiration -->
<uc-config 
  secure-uploads-expire-threshold="60000">
</uc-config>
Benefits:
  • Prevents upload failures due to expired signatures
  • Provides time buffer for token refresh
  • Reduces race conditions during expiration

Backend Implementation

You must implement a backend endpoint to generate signatures:
const express = require('express');
const crypto = require('crypto');
const app = express();

const UPLOADCARE_SECRET = process.env.UPLOADCARE_SECRET;

app.get('/api/uploadcare-signature', (req, res) => {
  // Set expiration time (e.g., 1 hour from now)
  const expire = Math.floor(Date.now() / 1000) + 3600;
  
  // Generate signature
  const signature = crypto
    .createHmac('sha256', UPLOADCARE_SECRET)
    .update(expire.toString())
    .digest('hex');
  
  res.json({
    secureSignature: signature,
    secureExpire: expire.toString(),
  });
});

app.listen(3000);
Never expose your secret key to the client. Always generate signatures on the server.

Complete Example

<!DOCTYPE html>
<html>
<head>
  <script type="module">
    import * as UC from 'https://cdn.jsdelivr.net/npm/@uploadcare/file-uploader@latest/web/file-uploader.min.js';
    UC.defineComponents(UC);
    
    window.addEventListener('DOMContentLoaded', () => {
      const config = document.getElementById('config');
      
      // Configure secure uploads
      config.secureUploadsSignatureResolver = async () => {
        try {
          const response = await fetch('/api/uploadcare-signature');
          
          if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
          }
          
          const data = await response.json();
          
          console.log('Signature obtained, expires:', 
            new Date(Number(data.secureExpire) * 1000).toISOString()
          );
          
          return {
            secureSignature: data.secureSignature,
            secureExpire: data.secureExpire,
          };
        } catch (error) {
          console.error('Failed to get signature:', error);
          
          // Show error to user
          alert('Failed to initialize secure uploads. Please try again.');
          
          return null;
        }
      };
      
      // Set expiration threshold to 60 seconds
      config.secureUploadsExpireThreshold = 60000;
      
      // Monitor upload events
      const provider = document.getElementById('provider');
      
      provider.addEventListener('file-upload-failed', (event) => {
        const file = event.detail;
        console.error('Upload failed:', file.errors);
        
        // Check for signature-related errors
        const signatureError = file.errors.find(
          e => e.type === 'UPLOAD_ERROR' && 
               e.message.includes('signature')
        );
        
        if (signatureError) {
          console.error('Signature error detected:', signatureError);
        }
      });
    });
  </script>
</head>
<body>
  <h1>Secure Upload Example</h1>
  
  <uc-file-uploader-regular ctx-name="uploader"></uc-file-uploader-regular>
  
  <uc-config 
    id="config"
    ctx-name="uploader"
    pubkey="YOUR_PUBLIC_KEY"
    multiple="true"
  ></uc-config>
  
  <uc-upload-ctx-provider 
    id="provider"
    ctx-name="uploader">
  </uc-upload-ctx-provider>
</body>
</html>

Debugging

Enable debug mode to see signature resolution logs:
<uc-config debug="true"></uc-config>
Logs will show:
  • When signatures are resolved
  • Expiration times
  • Errors during resolution
// Source: src/abstract/managers/SecureUploadsManager.ts:18-36
if (!this._secureToken) {
  this._debugPrint('Secure signature is not set yet.');
} else {
  this._debugPrint('Secure signature is expired. Resolving a new one...');
}

// After resolution
this._debugPrint('Secure signature resolved:', result);
this._debugPrint(
  'Secure signature will expire in',
  new Date(Number(result.secureExpire) * 1000).toISOString(),
);

Error Handling

Handle signature resolution failures gracefully:
config.secureUploadsSignatureResolver = async () => {
  try {
    const response = await fetch('/api/uploadcare-signature', {
      headers: {
        'Authorization': `Bearer ${userToken}`,
      },
    });
    
    if (!response.ok) {
      throw new Error(`Server returned ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Signature resolution failed:', error);
    
    // Notify user
    showErrorMessage('Unable to initialize secure uploads');
    
    // Optionally retry
    if (retryCount < 3) {
      await delay(1000);
      return retrySignatureResolution();
    }
    
    return null;
  }
};

API Reference

Configuration Properties

PropertyTypeDefaultDescription
secureSignaturestring''Static upload signature
secureExpirestring''Signature expiration timestamp
secureUploadsSignatureResolverSecureUploadsSignatureResolvernullDynamic signature resolver
secureUploadsExpireThresholdnumber(default)Time before expiration to refresh (ms)

Types

// Source: src/types/exported.ts:35-36
export type SecureUploadsSignatureAndExpire = {
  secureSignature: string;
  secureExpire: string;
};

export type SecureUploadsSignatureResolver = () => 
  Promise<SecureUploadsSignatureAndExpire | null>;

Best Practices

  • Never expose your secret key to the client
  • Generate signatures on your backend server
  • Use HTTPS for signature endpoints
  • Implement rate limiting on signature endpoints
  • Set reasonable expiration times (e.g., 1-24 hours)
  • Validate user authentication before generating signatures
Signatures are refreshed automatically when expired or near expiration. The threshold ensures uploads don’t fail due to expired tokens.
If signature resolution fails, uploads will not proceed. Always implement proper error handling and user feedback.

Next Steps

Build docs developers (and LLMs) love