Cloud Computing 10 min read

Direct Upload to OSS from Web & WeChat Mini‑Program via Server‑Side Signatures

This guide explains how to generate server‑side signatures for Alibaba Cloud OSS, obtain upload policies, and perform direct form‑based uploads from web browsers and WeChat mini‑programs, covering request flow, PHP helper functions, client‑side JavaScript integration, and OSS callback handling.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
Direct Upload to OSS from Web & WeChat Mini‑Program via Server‑Side Signatures

Overview

This tutorial demonstrates how to upload files directly from a web form to Alibaba Cloud Object Storage Service (OSS) without routing the data through an application server, thereby improving transmission efficiency.

Server‑Side Signature Direct Upload

In the server‑side signature approach the backend generates a signed upload policy, returns the policy and signature to the client, and the client uses these values to upload files directly to OSS. Access keys remain on the server, providing stronger security than client‑side signing.

Request Flow

The diagram below shows the request flow for server‑side signature direct upload:

Server‑side signature upload flow diagram
Server‑side signature upload flow diagram

Generating the Upload Policy Signature (PHP)

/**
 * @desc  Get upload policy signature
 * @param array $param
 * @return array
 */
public static function getUploadPolicy(array $param): array
{
    $param['type'] = 'images';
    $config = [
        'accessKeyId' => 'xxxxxxxxxxxxxxx',
        "accessKeySecret" => 'xxxxxxxxxxxxxxxxxxxxxxxxxxx',
        'callbackUrl' => 'https://oss.tinywan.com/aliyun/oss-upload-callback',
        'images' => [
            'bucket' => 'images',
            'host' => 'https://images.tinywan.com',
            'endpoint' => 'oss-cn-hangzhou.aliyuncs.com',
            'ContentLengthMin' => 10,
            'ContentLengthMax' => 20 * 1024 * 1024,
        ]
    ];
    $bucket = $config[$param['type']];
    // File path and name
    $dir = self::PROJECT_BUCKET . DIRECTORY_SEPARATOR . env('env_name') . DIRECTORY_SEPARATOR . self::getDirectoryPath($param['type'], $param['dirname']);
    $key = $dir . self::getRandomFilename($param['ext']);
    // Expiration time
    $expiration = self::getExpireTime(self::EXPIRE_TIME);
    // Policy parameters
    $policyParams = [
        'expiration' => $expiration,
        'conditions' => [
            ['starts-with', '$key', $dir],
            ['content-length-range', $bucket['ContentLengthMin'], $bucket['ContentLengthMax']]
        ]
    ];
    $policyBase64 = self::getPolicyBase64($policyParams);
    $signature = self::getSignature($policyBase64, $config['accessKeySecret']);
    // Callback configuration
    $callbackParam = [
        'callbackUrl' => $config['callbackUrl'],
        'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
        'callbackBodyType' => 'application/x-www-form-urlencoded',
    ];
    $callbackString = json_encode($callbackParam);
    $base64CallbackBody = base64_encode($callbackString);
    return [
        'access_id' => $config['accessKeyId'],
        'host' => 'https://' . $bucket['bucket'] . '.' . $bucket['endpoint'],
        'policy' => $policyBase64,
        'signature' => $signature,
        'expire' => $expiration,
        'callback' => $base64CallbackBody,
        'dir' => $dir,
        'key' => $key,
        'url' => $bucket['host'] . DIRECTORY_SEPARATOR . $key,
    ];
}

Helper Functions (PHP)

Get Policy Base64

/**
 * @desc: Get policy base64 string
 */
private static function getPolicyBase64($policyParams): string
{
    return base64_encode(json_encode($policyParams));
}

Get Signature

/**
 * @desc: Generate signature
 */
private static function getSignature(string $policyBase64, string $accessKeySecret): string
{
    return base64_encode(hash_hmac('sha1', $policyBase64, $accessKeySecret, true));
}

Get Expiration Time

/**
 * @desc: Get expiration timestamp
 */
private static function getExpireTime(int $time)
{
    return str_replace('+00:00', '.000Z', gmdate('c', time() + $time));
}

Get Directory Path by Month

/**
 * @desc: Build month‑based directory path
 */
private static function getDirectoryPath(string $type, string $directoryName): string
{
    if ($type === 'img') {
        return $directoryName . DIRECTORY_SEPARATOR . date('Y-m') . DIRECTORY_SEPARATOR;
    }
    return date('Y-m') . DIRECTORY_SEPARATOR;
}

Generate Random Filename

/**
 * @desc: Create a random filename with extension
 */
private static function getRandomFilename(string $extend): string
{
    return \Ramsey\Uuid\Uuid::uuid4()->toString() . '.' . $extend;
}

WeChat Mini‑Program Client

Request Parameters

{
  "type": "images",
  "dirname": "images",
  "ext": "png"
}

Response Example

{
  "code": 0,
  "msg": "success",
  "data": {
    "access_id": "xxxxxxxxxxxxxxxxx",
    "host": "https://tinywan-images.oss-cn-hangzhou.aliyuncs.com",
    "policy": "eyJlexxxxxxxxxxxxxxxxxxxx==",
    "signature": "YrlQxxxxxxxxxxxxxxxxxxxxxxtiI=",
    "expire": "2024-05-22T09:46:46.000Z",
    "callback": "eyJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0=",
    "dir": "ai/2024-05/",
    "key": "ai/2024-05/14b796b1-54ef-48d9-83a3-cde7cbc298e7.png",
    "url": "https://images.tinywan.com/ai/2024-05/14b796b1-54ef-48d9-83a3-cde7cbc298e7.png"
  }
}

Implementation (JavaScript for Mini‑Program)

/** Get file extension */
function getFilePathExtention(filePath) {
  return filePath.split('.').slice(-1)[0];
}
/** Upload to OSS */
function uploadFileAsync(config, filePath) {
  console.log(config);
  return new Promise((resolve, reject) => {
    wx.uploadFile({
      url: config.host,
      filePath: filePath,
      name: 'file',
      formData: {
        key: config.key,
        policy: config.policy,
        OSSAccessKeyId: config.accessKeyId,
        signature: config.signature
      },
      success: (res) => {
        if (res.statusCode === 204) {
          resolve();
        } else {
          reject('Upload failed');
        }
      },
      fail: (err) => console.log(err)
    });
  });
}
/** Main upload function */
export async function uploadFile(filePath, dirname = 'image') {
  const ext = getFilePathExtention(filePath);
  const resParams = await Http.AliOssGetUploadParams({ ext, dirname });
  await uploadFileAsync(resParams.data, filePath);
  return resParams;
}

OSS Upload Callback

After a file is uploaded, the application server often needs metadata such as file name, size, and image dimensions. OSS provides a callback mechanism that notifies the server once the upload succeeds.

Callback Flow

OSS callback flow diagram
OSS callback flow diagram

Reference Callback Code (PHP)

/**
 * @desc: OSS upload callback handler
 */
public function ossUploadCallback(): Response
{
    // 1. Retrieve request headers
    $header = $this->request->header();
    $authorizationBase64 = $header['authorization'] ?? '';
    $pubKeyUrlBase64 = $header['x-oss-pub-key-url'] ?? '';
    if ($authorizationBase64 == '' || $pubKeyUrlBase64 == '') {
        throw new \tinywan\exception\ForbiddenHttpException();
    }
    // 2. Decode OSS signature
    $authorization = base64_decode($authorizationBase64);
    // 3. Get public key
    $pubKeyUrl = base64_decode($pubKeyUrlBase64);
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $pubKeyUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    $publicKey = curl_exec($ch);
    if ($publicKey == '') {
        throw new \tinywan\exception\ForbiddenHttpException();
    }
    // 4. Get callback body
    $body = $this->request->getInput();
    // 5. Build string to verify
    $path = $_SERVER['REQUEST_URI'];
    $pos = strpos($path, '?');
    if ($pos === false) {
        $authStr = urldecode($path) . "
" . $body;
    } else {
        $authStr = urldecode(substr($path, 0, $pos)) . substr($path, $pos) . "
" . $body;
    }
    // 6. Verify signature
    $verifyRes = openssl_verify($authStr, $authorization, $publicKey, OPENSSL_ALGO_MD5);
    if ($verifyRes === 1) {
        return response_json('success', 200, $this->request->post());
    }
    throw new \tinywan\exception\ForbiddenHttpException();
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

WeChat Mini ProgramPHPcloud storageOSSServer-side signature
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.