Prerequisites
Create a OSS bucket and collect the following credentials from the RAM console:
AccessKeyIdandAccessKeySecret(store the secret immediately, it is shown only once)- Bucket name and
endpoint(found in the OSS console under "Bucket Overview") - Attach the
AliyunOSSFullAccesspolicy (or a scoped custom policy) to the RAM user.
Pattern 1 – Server-Signed Direct Upload
The browser uploads straight to OSS while the backend only signs the policy. No secret ever leaves the server.
1. CORS Setup
In the OSS console add a CORS rule:
Allowed Origin: https://your-domain.com
Allowed Method: POST, PUT
Allowed Header: *
Expose Header: ETag
2. Backend Signature Endpoint (Node.js example)
import crypto from 'crypto';
import express from 'express';
const app = express();
const cfg = {
accessKeyId: process.env.OSS_KEY,
accessKeySecret: process.env.OSS_SECRET,
bucket: 'my-bucket',
region: 'oss-cn-hangzhou'
};
function iso8601(exp) {
return new Date(exp * 1000).toISOString().replace(/\.\d{3}Z$/, 'Z');
}
app.get('/sign', (req, res) => {
const dir = (req.query.dir || 'uploads/').replace(/^\/|\/$/g, '') + '/';
const now = Math.floor(Date.now() / 1000);
const exp = now + 60; // 1-minute window
const policy = {
expiration: iso8601(exp),
conditions: [
['content-length-range', 0, 100 * 1024 * 1024], // 100 MB
['starts-with', '$key', dir]
]
};
const policyB64 = Buffer.from(JSON.stringify(policy)).toString('base64');
const signature = crypto
.createHmac('sha1', cfg.accessKeySecret)
.update(policyB64)
.digest('base64');
res.json({
host: `https://${cfg.bucket}.${cfg.region}.aliyuncs.com`,
dir,
policy: policyB64,
signature,
accessid: cfg.accessKeyId,
expire: exp
});
});
app.listen(3000);
3. Front-End Upload (Vue 3 + Element Plus)
<template>
<el-upload
:action="host"
:data="form"
:before-upload="prepare"
:on-success="done">
<el-button type="primary">Upload</el-button>
</el-upload>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const host = ref('');
const form = ref({});
async function prepare(file) {
const { data } = await axios.get('/sign', { params: { dir: 'avatars' } });
host.value = data.host;
form.value = {
key: data.dir + file.name,
policy: data.policy,
OSSAccessKeyId: data.accessid,
signature: data.signature,
success_action_status: 200
};
return true;
}
function done() { /* handle success */ }
</script>
Pattern 2 – STS Temporary Credentials
Issue short-lived tokens so the browser never sees long-term secrets and can still use the OSS SDK.
1. STS Token Service (Node.js)
import STS from 'ali-oss/lib/sts.js';
import express from 'express';
const app = express();
const sts = new STS({
accessKeyId: process.env.RAM_KEY,
accessKeySecret: process.env.RAM_SECRET
});
app.get('/sts', async (_req, res) => {
try {
const result = await sts.assumeRole(
'acs:ram::123456789:role/oss-upload',
'', // custom policy (optional)
3600, // 1 hour
'web-' + Date.now()
);
res.json({
accessKeyId: result.credentials.AccessKeyId,
accessKeySecret: result.credentials.AccessKeySecret,
stsToken: result.credentials.SecurityToken,
bucket: 'my-bucket',
region: 'oss-cn-hangzhou'
});
} catch (e) {
res.status(500).send(e.message);
}
});
app.listen(3000);
2. Front-End Upload with ali-oss SDK
import OSS from 'ali-oss';
let client;
async function initClient() {
const { data } = await axios.get('/sts');
client = new OSS({
region: data.region,
accessKeyId: data.accessKeyId,
accessKeySecret: data.accessKeySecret,
stsToken: data.stsToken,
bucket: data.bucket,
refreshSTSToken: async () => {
const { data: d } = await axios.get('/sts');
return {
accessKeyId: d.accessKeyId,
accessKeySecret: d.accessKeySecret,
stsToken: d.stsToken
};
},
refreshSTSTokenInterval: 300000
});
}
async function uploadFile(file) {
await initClient();
const name = 'docs/' + file.name;
return client.multipartUpload(name, file, {
progress: p => console.log('Progress', Math.floor(p * 100) + '%')
});
}
Pattern 3 – Client-Side Policy & Signature (PostObject)
Useful when you rely on third-party upload widgets that only accept a simple form action URL.
import CryptoJS from 'crypto-js';
import Base64 from 'js-base64';
function buildPolicy(secret, maxSize = 104857600) {
const policy = {
expiration: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
conditions: [['content-length-range', 0, maxSize]]
};
const policyBase64 = Base64.encode(JSON.stringify(policy));
const signature = CryptoJS.HmacSHA1(policyBase64, secret).toString(CryptoJS.enc.Base64);
return { policy: policyBase64, signature };
}
Populate the form before upload:
<el-upload
action="https://my-bucket.oss-cn-hangzhou.aliyuncs.com"
:data="extra">
</el-upload>
extra = {
key: 'uploads/${filename}',
...buildPolicy(stsSecret)
};
Pattern 4 – Direct SDK in Browser (Not Recommended)
Embedding long-term AccessKeyId/Secret in the page leaks credentials. Use only for throw-away demos.
Pattern 5 – Proxy via Application Server
File hits your backend first, then the backend streams it to OSS. Works to small files; large files require chunking and higher server limits.
// PHP snippet using aliyun-oss-php-sdk
$oss = new \OSS\OssClient($key, $secret, $endpoint);
$oss->uploadFile($bucket, $object, $_FILES['file']['tmp_name']);
Common Pitfalls
- CORS errors – ensure
PUTandPOSTare allowed andETagis exposed. - Mising
x-oss-security-tokenwhen using STS. - Element-UI progress not updating – supply
onProgressinsidehttp-request.