3 Oct 2023

Download Derivative Files using new signedcookies API without setting cookies first in the header

Default blog image

Have you ever considered whether there is an easy way to download derivative files via Model Derivative API? 

 

In the last step of our tutorial Extract Geometry (OBJ) from a Source File, it requires using GET /{urn}/manifest/{derivativeUrn}/signedcookies to get the download URL and signed cookies to let you securely download the derivative specified by the derivativeUrn URI parameter, but you would have a hard time while configuring cookies in the request header to download OBJ file, especially you want to download the OBJ file directly from the web browser. No worry, we've got you covered!

We can download the derivative files without configuring the cookies first. To do so, we must extract and convert the signed cookies we obtained from the response of GET /{urn}/manifest/{derivativeUrn}/signedcookies into query string parameters and append them into the download URL. (Special thanks to our friend Tom, who kindly shared this tip in this StackOverflow thread.)

(Note. Not just OBJ, this approach also can be applied to download the IFC or DWG we translated from RVT using Model Derivative API)

 

Here are the core ideas and steps:

1. Obtain signed Cookies like step 3 of the tutorial Extract Geometry (OBJ) from a Source File. For example:

# Reequest
curl -X GET \
     'https://developer.api.autodesk.com/modelderivative/v2/designdata/dXJuOmFkc2sub2JqZW....xlX3Byb2plY3QucnZ0/manifest/urn%3Aadsk.viewing%3Afs.file%3AdXJuOmFkc2sub2JqZW....xlX3Byb2plY3QucnZ0%2Foutput%2Fgeometry%2F9154b91d-fc0e-3fc2-b904-ff5cf58132db.obj/signedcookies' \
     -H 'Authorization: Bearer <YOUR_ACCESS_TOKEN>'

# Response
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Date: Thu, 26 May 2022 03:57:31 GMT
Set-Cookie: CloudFront-Policy=eyJTdGF0ZW1lbnQiO...zQ3MDg3fX19XX0_; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly
Set-Cookie: CloudFront-Key-Pair-Id=APKAI...EURDQ; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly
Set-Cookie: CloudFront-Signature=EEN9GD...-vxFIe1D~g__; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Accept-Encoding
x-ads-app-identifier: platform-viewing-2022.05.01.75.c20538dec-production
x-ads-duration: 37 ms
x-ads-region: US
x-ads-startup-time: Tue May 24 06:34:02 UTC 2022
Content-Length: 260
Connection: keep-alive

{
    "etag": "1b2b87eeba06472a166cae60eda14f8d",
    "size": 79577,
    "url": "https://cdn.derivative.autodesk.com/dXJuOmFkc2sub2JqZW....xlX3Byb2plY3QucnZ0/output/geometry/9154b91d-fc0e-3fc2-b904-ff5cf58132db.obj",
    "content-type": "application/x-tgif",
    "expiration": 1653578910721
}

2. Extract the download URL from the response body and signed cookies from the response header

# Singed cookies from resposne header
Set-Cookie: CloudFront-Policy=eyJTdGF0ZW1lbnQiO...zQ3MDg3fX19XX0_; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly
Set-Cookie: CloudFront-Key-Pair-Id=APKAI...EURDQ; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly
Set-Cookie: CloudFront-Signature=EEN9GD...-vxFIe1D~g__; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly

# Download URL from resposne body
https://cdn.derivative.autodesk.com/dXJuOmFkc2sub2JqZW....xlX3Byb2plY3QucnZ0/output/geometry/9154b91d-fc0e-3fc2-b904-ff5cf58132db.obj

3. Convert signed cookies into query strings

# For query string `Policy`, we only need the value between `Set-Cookie: CloudFront-Policy=` and `; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly`, so the value of query string `Policy` is:
eyJTdGF0ZW1lbnQiO...zQ3MDg3fX19XX0_

# For query string `Key-Pair-Id`, we only need the value between `Set-Cookie: CloudFront-Key-Pair-Id=` and `; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly`, so the value of query string `Key-Pair-Id` is:
APKAI...EURDQ

# For query string `Signature`, we only need the value between `Set-Cookie: CloudFront-Policy=` and `; Path=/; Domain=cdn.derivative.autodesk.com; HTTPOnly`, so the value of query string `Signature` is:
EEN9GD...-vxFIe1D~g

4. Append query strings from step 3 to the download URL

https://cdn.derivative.autodesk.com/dXJuOmFkc2sub2JqZW....xlX3Byb2plY3QucnZ0/output/geometry/9154b91d-fc0e-3fc2-b904-ff5cf58132db.obj?Policy=eyJTdGF0ZW1lbnQiO...zQ3MDg3fX19XX0_&Key-Pair-Id=APKAI...EURDQ&Signature=EEN9GD...-vxFIe1D~g

5. Download the file via the new URL we got from Step 4. For example, when downloading the file from Web Browser directly, we can pass the new download URL to window.open like the below way:

// In JavaScript

window.open('https://cdn.derivative.autodesk.com/dXJuOmFkc2sub2JqZW....xlX3Byb2plY3QucnZ0/output/geometry/9154b91d-fc0e-3fc2-b904-ff5cf58132db.obj?Policy=eyJTdGF0ZW1lbnQiO...zQ3MDg3fX19XX0_&Key-Pair-Id=APKAI...EURDQ&Signature=EEN9GD...-vxFIe1D~g');

 

Code Example

var restSharpClient = new RestClient("https://developer.api.autodesk.com");

RestRequest request = new RestRequest("/modelderivative/v2/designdata/{urn}/manifest/{derivativeUrn}/signedcookies", RestSharp.Method.Get);
request.AddHeader("Authorization", "Bearer " + accessToken);
request.AddUrlSegment("urn", urn);
request.AddUrlSegment("derivativeUrn", derivativeUrn);

var response = await restSharpClient.ExecuteAsync(request);

var cloudFrontPolicyName = "CloudFront-Policy";
var cloudFrontKeyPairIdName = "CloudFront-Key-Pair-Id";
var cloudFrontSignatureName = "CloudFront-Signature";

var cloudFrontCookies = response.Headers
                        .Where(x => x.Name.ToLower() == "set-cookie") //!<<< Header field case should be incase-sensitive according to https://www.rfc-editor.org/rfc/rfc7230#section-3.2
                        .Select(x => x.Value)
                        .Cast<string>()
                        .ToList();

var cloudFrontPolicy = cloudFrontCookies.Where(value => value.Contains(cloudFrontPolicyName)).FirstOrDefault()?.Trim().Substring(cloudFrontPolicyName.Length + 1).Split(";").FirstOrDefault();
var cloudFrontKeyPairId = cloudFrontCookies.Where(value => value.Contains(cloudFrontKeyPairIdName)).FirstOrDefault()?.Trim().Substring(cloudFrontKeyPairIdName.Length + 1).Split(";").FirstOrDefault();
var cloudFrontSignature = cloudFrontCookies.Where(value => value.Contains(cloudFrontSignatureName)).FirstOrDefault()?.Trim().Substring(cloudFrontSignatureName.Length + 1).Split(";").FirstOrDefault();

var result = JsonConvert.DeserializeObject<dynamic>(response.Content);
var downloadURL = $"{result.url}?Key-Pair-Id={cloudFrontKeyPairId}&Signature={cloudFrontSignature}&Policy={cloudFrontPolicy}";

System.Diagnostics.Trace.WriteLine(downloadURL);

RestRequest requestDownload = new RestRequest(downloadURL, RestSharp.Method.Get);
System.IO.Stream downloadStream = await restSharpClient.DownloadStreamAsync(requestDownload);
string name = derivativeUrn.Substring(derivativeUrn.LastIndexOf('/') + 1);
using (var memoryStream = new MemoryStream())
{
    downloadStream.CopyTo(memoryStream);
    File.WriteAllBytes(name, memoryStream.ToArray());
}

 

  • For Node.js/JavaScript using fetch:
async function getDerivativeUrlWithQueryStrings(urn, derivativeUrn) {
  const response = await fetch(
    `https://developer.api.autodesk.com/modelderivative/v2/designdata/${urn}/manifest/${derivativeUrn}/signedcookies`,
    {
      method: "GET",
      headers: {
        Authorization: `Bearer ${await getAccessToken()}`,
      },
    }
  );
  const data = await response.json();
  let url = data.url + "?";

  // In this case we get all 3 'Set-Cookie' headers in a single string
  // separated by commas. We need to split them up and set them individually
  const cookie = response.headers.get("Set-Cookie").replaceAll(",", ";");
  const cookies = cookie.split("; ");
  for (const cookie of cookies) {
    let [name, value] = cookie.split("=");
    if (name.startsWith("CloudFront-")) {
      name = name.replace("CloudFront-", "");
      url += `${name}=${value}&`;
    }
  }

  return url;
}

 

Enjoy it!

Related Article