Signature Authentication
As briefly mentioned in our concepts, Signature Authentication is a security measure that can prevent outsiders from tampering with your Assembly Instructions. It provides trust in untrusted environments.
Warning: We strongly recommend enabling Signature Authentication when interfacing with our API, particularly in untrusted environments where users may be able to access your Auth Key.
Given that there are only two parties in the world that have your account's Auth Secret (Transloadit and you), we can leverage that to generate a cryptographic signature on both our ends for the data that we exchange. We compare signatures after receiving a message, and know this exact message could have only come from someone that has the secret.
Since the signature is calculated via one-way encryption, the signature itself is not a secret, and someone seeing it could not derive the Auth Secret used to generate it. That's great because signatures are generated by servers that can keep a secret safe, and then injected into web browsers that don't share that quality.
For creating Assemblies with Transloadit, your back-end could calculate a Signature that only covers certain parameters, authenticated users, and a timeframe that it deems legitimate usage. For instance, it would refuse to generate a signature for users that are not logged in. You could use any business logic on the server-side here to decide if you hand out a signature, or not, and Transloadit can be configured to reject any request for your Account that is not accompanied by a correct signature for the payload.
If you want to make Signature Authentication mandatory for all requests concerning your account:
- Go to the Workspace Settings in your account.
- In the API Settings section, enable the Require a correct Signature option.
- Hit the Save button.
Note: Most back-end SDKs automatically use Signature Authentication when you supply your Auth Secret. So perhaps, just this introduction is all you need to know. If you are integrating Transloadit into untrusted environments, however, such as browsers (Uppy!), you'll want to continue reading to see how your back-end can supply signatures to it.
How to generate Signatures
So, how does this all look?
The typical params
field when creating an Assembly without Signature Authentication
is as follows:
{
"auth": {
"key": "23c96d084c744219a2ce156772ec3211"
},
"steps": { ... }
}
The auth.key
in this example is the Auth Key from
API Credentials in your account.
To sign this request, the additional auth.expires
field needs to be added. This adds it to our
payload, which is protected by our signature. If someone would change it, Transloadit would reject
the request as the signature no longer matches. You signed a different payload than the one we
received. If the signature does match, then we will naturally compare and reject by date as
instructed. This way, requests become very hard to indefinitely repeat by a third party that got a
hold of this payload. Because even though our A+ grade HTTPS should already go a long way in
preventing that, browser cache could be easier to snoop on.
The expires
property must contain a timestamp in the (near) future. Use
YYYY/MM/DD HH:mm:ss+00:00
as the date format, making sure that UTC is used for the timezone. For
example:
{
"auth": {
"key": "23c96d084c744219a2ce156772ec3211",
"expires": "2024/01/31 16:53:14+00:00"
},
"steps": { ... }
}
To calculate the signature for this request:
- From your front-end, stringify the above JavaScript object into JSON and send it to your back-end.
- From your back-end, calculate an RFC 6234-compliant HMAC
hex signature on the string, with your Auth Secret as the key, and SHA384 as the hash
algorithm. Prefix the
signature
string with the algorithm name in lowercase. For example, for SHA384, usesha384:<HMAC-signature>
. You can send that string to your front-end (as long as you made the appropriate checks to guarantee it was a genuine request from your front-end). - From your front-end, add a
signature
multipart POST field containing this value to your request (e.g., with a hidden field in an HTML form).
Note: If your implementation uses a template_id
instead of steps
, there's no need to
generate a signature for the Instructions that your Template contains. We
should only sign communication payloads.
Important: To generate a signature for Smart CDN URLs please use the auth key designated for
Smart CDN usage from your Credentials page when logged in. For those signatures you must use the
sha256
algorithm. This is due to restrictions from Amazon Cloudfront which we use as a CDN. For
non-Smart CDN signatures please use sha384
.
Signature Authentication is offered on requests that you send to Transloadit, but also the other way around. For instance, in Async Mode you will want to ensure that any Assembly Notifications that Transloadit sends to your back-end are actually coming from us. The example Node.js code in the Assembly Notifications docs illustrates this flow, and we have example code for generating signatures in different languages right below.
Example code for different languages
When you deal with JSON, please keep in mind that your language of choice might escape some
characters (i.e. it might turn occurrences of /
into \/
, or é
into "\\u00e9"
). We calculate
the signatures on our end with unescaped strings! Please make sure to remove backslashes from
your JSON before calculating its signature.
If you use PHP for example, please check the JSON_UNESCAPED_SLASHES
option of the
json_encode function.
const crypto = require('node:crypto')
const utcDateString = (ms) => {
return new Date(ms)
.toISOString()
.replace(/-/g, '/')
.replace(/T/, ' ')
.replace(/\.\d+Z$/, '+00:00')
}
// expire 1 hour from now (this must be milliseconds)
const expires = utcDateString(Date.now() + 1 * 60 * 60 * 1000)
const authKey = 'YOUR_TRANSLOADIT_KEY'
const authSecret = 'YOUR_TRANSLOADIT_SECRET'
const params = JSON.stringify({
auth: {
key: authKey,
expires,
},
template_id: 'YOUR_TRANSLOADIT_TEMPLATE_ID',
// your other params like template_id, notify_url, etc.
})
const signatureBytes = crypto.createHmac('sha384', authSecret).update(Buffer.from(params, 'utf-8'))
// The final signature needs the hash name in front, so
// the hashing algorithm can be updated in a backwards-compatible
// way when old algorithms become insecure.
const signature = `sha384:${signatureBytes.digest('hex')}`
console.log(`${expires} ${signature}`)
<?php
// expire 1 hours from now
$expires = gmdate('Y/m/d H:i:s+00:00', strtotime('+1 hour'));
$authKey = 'YOUR_TRANSLOADIT_KEY';
$authSecret = 'YOUR_TRANSLOADIT_SECRET';
$params = json_encode(
[
'auth' => [
'key' => $authKey,
'expires' => $expires,
],
'template_id' => 'YOUR_TRANSLOADIT_TEMPLATE_ID',
],
JSON_UNESCAPED_SLASHES
);
$signature = hash_hmac('sha384', $params, $authSecret);
echo $expires . ' sha384:' . $signature . PHP_EOL;
require 'rubygems'
require 'openssl'
require 'json'
# expire one hour from now
expires = (Time.now.utc + 1 * 60 * 60).strftime('%Y/%m/%d %H:%M:%S+00:00')
auth_key = 'YOUR_TRANSLOADIT_KEY'
auth_secret = 'YOUR_TRANSLOADIT_SECRET'
params = JSON.generate({
:auth => {
:key => auth_key,
:expires => expires,
},
:template_id => 'YOUR_TRANSLOADIT_TEMPLATE_ID',
})
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha384'), auth_secret, params)
puts(expires + " sha384:" + signature)
import hmac
import hashlib
import json
from datetime import datetime, timedelta
expires = (timedelta(seconds=60 * 60) + datetime.utcnow()).strftime("%Y/%m/%d %H:%M:%S+00:00")
auth_key = 'YOUR_TRANSLOADIT_KEY'
auth_secret = 'YOUR_TRANSLOADIT_SECRET'
params = {
'auth': {
'key': auth_key,
'expires': expires,
},
'template_id': 'YOUR_TRANSLOADIT_TEMPLATE_ID'
# your other params like template_id, notify_url, etc.
}
message = json.dumps(params, separators=(',', ':'), ensure_ascii=False)
signature = hmac.new(auth_secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha384).hexdigest()
print(expires, "sha384:" + signature)
package com.transloadit.sdk;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class JavaSignature {
public static void main(String[] args) {
String authKey = "YOUR_TRANSLOADIT_KEY";
String authSecret = "YOUR_TRANSLOADIT_SECRET";
String templateId = "YOUR_TRANSLOADIT_TEMPLATE_ID";
DateTimeFormatter formatter = DateTimeFormat.forPattern("Y/MM/dd HH:mm:ss+00:00").withZoneUTC();
String expiry = formatter.print(Instant.now().plus(60 * 60 * 1000));
String messageFormat = "{\"auth\":{\"key\":\"%s\",\"expires\":\"%s\"},\"template_id\":\"%s\"}";
String message = String.format(messageFormat, authKey, expiry, templateId);
byte[] kSecret = authSecret.getBytes(Charset.forName("UTF-8"));
byte[] rawHmac = HmacSHA384(kSecret, message);
byte[] hexBytes = new Hex().encode(rawHmac);
System.out.println(expiry + " sha384:" + new String(hexBytes, Charset.forName("UTF-8")));
}
private static byte[] HmacSHA384(byte[] key, String data) {
final String ALGORITHM = "HmacSHA384";
Mac mac;
try {
mac = Mac.getInstance(ALGORITHM);
mac.init(new SecretKeySpec(key, ALGORITHM));
return mac.doFinal(data.getBytes(Charset.forName("UTF-8")));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}
Smart CDN
To sign a Smart CDN URL, a similar process as for regular API signatures is used. A HMAC digest is calculated on a string, that is derived from the Smart CDN URL, with the Auth Secret as the key. For the signature to be valid, the used Auth Key must be enabled for Smart CDN use.
The generation of a Smart CDN signature must be performed on the back-end. The process uses the Auth Secret, which is confidential and must not be exposed to your users on the front-end.
A typical Smart CDN URL has the following structure:
https://[your-workspace].tlcdn.com/[template-name]/[file-path]?[parameters]
[your-workspace]
is your Transloadit Workspace name[template-name]
is the name of your Template[file-path]
is the path to the file you want to transform[parameters]
are desired transformation parameters (e.g.h=100
)
A signature for this URL is generated by following these steps:
- Add the
exp
query parameter for defining a time in the future after which the signature is not accepted by the Smart CDN anymore. This is useful to limit temporal access to a file. The expiration moment is represented by the number of milliseconds since the UNIX epoch (the midnight at the beginning of January 1, 1970, UTC). While this parameter is optional, we highly recommend always setting an expiration time. For example, a signature usingexp=1722517200000
is valid until Thu, 01 Aug 2024 13:00:00 GMT. - Add the
auth_key
query parameter to define the Auth Key corresponding to the Auth Secret that is used to create the signature. If this parameter is not set, Transloadit's API assumes that the oldest, Smart-CDN-enabled Auth Key pair has been used for the signature. Setting theauth_key
parameters allows you to rotate your Auth Key without interrupting your users, and we thus highly recommend setting it. For example:auth_key=23c96d084c744219a2ce156772ec3211
- Sort the query parameters according to unicode code points of the keys in descending order. The
sorting should be stable, i.e. if a key appears multiple times in the query string, the
corresponding values should retain their relative ordering. For example,
h=100&f=png&f=jpg&auth_key=hello&exp=123
is sorted intoauth_key=hello&exp=123&h=100&f=png&f=jpg
. - Construct the string to sign by concatenating the values:
[your-workspace]/[template-name]/[file-path]?[sorted-parameters]
[your-workspace]
,[template-name]
, and[file-path]
must be URL-encoded to ensure they only contain URL-safe characters. Note that the string does not start with a leading slash. The?
character must be omitted if[sorted-parameters]
is empty. - Calculate an RFC 6234-compliant HMAC hex signature on the
string to sign, with your Auth Secret as the key, and SHA256 as the hash algorithm.
Prefix the hex signature with the algorithm name in lowercase and a colon, i.e.
sha256
. For example, for SHA256, usesha256:[hmac-signature]
. - Append the prefixed hex signature under the
sig
query parameter to the URL, giving the signed Smart CDN URL:https://[your-workspace].tlcdn.com/[template-name]/[file-path]?[parameters]&sig=sha256:[hmac-signature]
[your-workspace]
,[template-name]
, and[file-path]
must be URL-encoded to ensure they only contain URL-safe characters. This signed URL can then be sent to or used in your front-end until the expiration date is reached.
Example code
Below you can find examples in different languages for generating signed Smart CDN URLs using our SDKs.
const crypto = require('node:crypto')
export function signedSmartCDNUrl(workspaceSlug, templateSlug, inputField, params = {}) {
const AUTH_KEY = 'YOUR_TRANSLOADIT_KEY'
const AUTH_SECRET = 'YOUR_TRANSLOADIT_SECRET'
const encodedWorkspaceSlug = encodeURIComponent(workspaceSlug)
const encodedTemplateSlug = encodeURIComponent(templateSlug)
const encodedInputField = encodeURIComponent(inputField)
const queryParams = new URLSearchParams(params)
queryParams.set('auth_key', AUTH_KEY)
queryParams.set('exp', `${Date.now() + 1 * 60 * 60 * 1000}`) // 1 hour
queryParams.sort()
const stringToSign = `${encodedWorkspaceSlug}/${encodedTemplateSlug}/${encodedInputField}?${queryParams}`
const algorithm = 'sha256'
const signature = crypto.createHmac(algorithm, AUTH_SECRET).update(stringToSign).digest('hex')
const signedUrl = `https://${encodedWorkspaceSlug}.tlcdn.com/${encodedTemplateSlug}/${encodedInputField}?${queryParams}&sig=${algorithm}:${signature}`
return signedUrl
}
Above you can find an example for generating a signed Smart CDN URL in Node.js. Please replace the placeholders for the Auth Key and Secret, and call the function with your own values for the Workspace slug, Template slug, input field, and parameters.
Currently, our Node SDK does not offer a dedicated function for generating signed Smart CDN URLs, but we are working on making such a helper function available in the upcoming release.
// composer require transloadit/php-sdk
require 'vendor/autoload.php';
use transloadit\Transloadit;
$transloadit = new Transloadit([
'key' => 'YOUR_TRANSLOADIT_KEY',
'secret' => 'YOUR_TRANSLOADIT_SECRET',
]);
$url = $transloadit->signedSmartCDNUrl(
'YOUR_WORKSPACE',
'YOUR_TEMPLATE',
'image.png',
[
'width' => 100,
'height' => 100
],
);
echo $url;
Above you can find an example for generating a signed Smart CDN URL using the PHP SDK. Please replace the placeholders for the Auth Key, Secret, Workspace slug, Template slug, input field, and parameters with your own values.
Alternatively, if you do not want to use an SDK, you can copy the SDK's implementation and place it directly in your code.
# gem install transloadit
require 'transloadit'
transloadit = Transloadit.new(
:key => 'YOUR_TRANSLOADIT_KEY',
:secret => 'YOUR_TRANSLOADIT_SECRET'
)
url = transloadit.signed_smart_cdn_url(
workspace: "YOUR_WORKSPACE",
template: "YOUR_TEMPLATE",
input: "image.png",
url_params: {
width: 100,
height: 100
}
)
Above you can find an example for generating a signed Smart CDN URL using the Ruby SDK. Please replace the placeholders for the Auth Key, Secret, Workspace slug, Template slug, input field, and parameters with your own values.
Alternatively, if you do not want to use an SDK, you can copy the SDK's implementation and place it directly in your code.
# pip install pytransloadit
from transloadit import client
tl = client.Transloadit('YOUR_TRANSLOADIT_KEY', 'YOUR_TRANSLOADIT_SECRET')
url = tl.get_signed_smart_cdn_url(
workspace="YOUR_WORKSPACE",
template="YOUR_TEMPLATE",
input="image.png",
url_params={
"height": 100,
"width": 100,
}
)
print(url)
Above you can find an example for generating a signed Smart CDN URL using the Python SDK. Please replace the placeholders for the Auth Key, Secret, Workspace slug, Template slug, input field, and parameters with your own values.
Alternatively, if you do not want to use an SDK, you can copy the SDK's implementation and place it directly in your code.
// go get github.com/transloadit/go-sdk
package main
import (
"fmt"
"net/url"
transloadit "github.com/transloadit/go-sdk"
)
func main() {
fmt.Println(GetSmartCDNUrl())
}
func GetSmartCDNUrl() string {
options := transloadit.DefaultConfig
options.AuthKey = "YOUR_TRANSLOADIT_KEY"
options.AuthSecret = "YOUR_TRANSLOADIT_SECRET"
client := transloadit.NewClient(options)
params := url.Values{}
params.Add("height", "100")
params.Add("width", "100")
url := client.CreateSignedSmartCDNUrl(transloadit.SignedSmartCDNUrlOptions{
Workspace: "YOUR_WORKSPACE",
Template: "YOUR_TEMPLATE",
Input: "image.png",
URLParams: params,
})
return url
}
Above you can find an example for generating a signed Smart CDN URL using the Go SDK. Please replace the placeholders for the Auth Key, Secret, Workspace slug, Template slug, input field, and parameters with your own values.
Alternatively, if you do not want to use an SDK, you can copy the SDK's implementation and place it directly in your code.
// implementation 'com.transloadit.sdk:transloadit:1.0.1'
package com.transloadit.examples;
import com.transloadit.sdk.Transloadit;
import com.transloadit.sdk.exceptions.LocalOperationException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) throws LocalOperationException {
System.out.println(getSmartCDNUrl());
}
public static String getSmartCDNUrl() throws LocalOperationException {
Transloadit transloadit = new Transloadit("YOUR_TRANSLOADIT_KEY", "YOUR_TRANSLOADIT_SECRET");
Map<String, List<String>> params = new HashMap<>();
params.put("height", Collections.singletonList("100"));
params.put("with", Collections.singletonList("100"));
return transloadit.getSignedSmartCDNUrl(
"YOUR_WORKSPACE",
"YOUR_TEMPLATE",
"image.png",
params
);
}
}
Above you can find an example for generating a signed Smart CDN URL using the Java SDK. Please replace the placeholders for the Auth Key, Secret, Workspace slug, Template slug, input field, and parameters with your own values.
Alternatively, if you do not want to use an SDK, you can copy the SDK's implementation and place it directly in your code.