Nuxt.js から Node.js に S3 の署名付き URL をリクエストするような状況でつまったところの忘備録です。
TL;DR
ドメインが違う → CORS 設定に気をつけよう
サーバーサイドに Node.js 使ってる → SDK の署名バージョンに気をつけよう
フロントに Axios 使ってる → 余計なヘッダーを送らないように気をつけよう
s3 の Bucket の CORS 設定をする
s3 のドメインとサイトのドメインが異なるなら設定しておきましょう。
アップロードのみできれば良いなら許可は PUT だけで良いと思います。
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
リージョンを指定する
署名バージョンを 4 に指定する
無いと失敗するようです…?
AWS_DEFAULT_REGION などは内部で勝手に使ってくれると思っていたのですが指定したほうが良さそうです。
// on server
const sts = new STS({
accessKeyId: process.env.ACCESS_KEY_ID,
secretAccessKey: process.env.SECRET_ACCESS_KEY,
region: process.env.AWS_DEFAULT_REGION, // ★
})
app.get('/get-signed-url', async (req, res) => {
const { Credentials } = await sts
.assumeRole({
RoleArn: process.env.STS_ROLE_ARN,
RoleSessionName: process.env.STS_ROLE_SESSION_NAME,
})
.promise()
if (!Credentials) return Promise.reject(new Error('failed to get credencials'))
const s3 = new S3({
accessKeyId: Credentials.AccessKeyId,
secretAccessKey: Credentials.SecretAccessKey,
sessionToken: Credentials.SessionToken,
region: process.env.AWS_DEFAULT_REGION, // ★
signatureVersion: 'v4', // ★
})
const Key = randomString(128)
const putUrl = await s3.getSignedUrlPromise('putObject', {
Bucket,
Key,
ContentType: req.query.mimetype,
Metadata: { 'user-name': 'hogehoge' },
Expires: 60,
})
res.json({ putUrl, Key })
})
余計なヘッダーを送らないようにする
put するときに余計なヘッダーをつけて送ると 403 SignatureDoesNotMatch が返ってきます。
Axios のデフォルトの headers に Authorization を入れている場合などは注意が必要だと思います。
// front
export default Vue.extend({
methods: {
async upload(rawFile: File) {
const prepareUrl = new URL(process.env.API_URL + '/get-signed-url')
prepareUrl.searchParams.append('filename', rawFile.name)
prepareUrl.searchParams.append('mimetype', rawFile.type)
this.$axios.setToken(this.$store.getters['auth/accessToken'], 'Bearer')
const { putUrl } = await this.$axios.$get(prepareUrl.toString())
this.$axios.setToken(false) // ★ Authorization ヘッダーが送られないように
await this.$axios.$put(putUrl, rawFile, {
headers: {
'Content-Type': rawFile.type,
// 'X-Amz-Acl': 'private', // ★ 間違えてつけてると 403 になります
}
})
}
}
})
参考
https://qiita.com/kochizufan/items/444b7e1dfcf568945410
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/cors.html
https://qiita.com/kyasbal_1994/items/774be5624e4d90c5cf38
https://qiita.com/segur/items/7746ac352afb5152cf46