Deno way of creating adobe JWT token?

Viewed 27
2 Answers

This is the working solution I came up with, after a lot of debugging. However, a proper dependency for that would be very welcome!

I'm pretty sure that a lot of people have a need for something like that.

import { create } from "https://deno.land/x/djwt@v2.7/mod.ts";
import "https://deno.land/x/dotenv/load.ts";

const clientId = Deno.env.get("client_id") || "";
const clientSecret = Deno.env.get("client_secret") || "";
const technicalAccountId = Deno.env.get("technical_account_id") || "";
const orgId = Deno.env.get("org_id") || "";
const metaScopes = JSON.parse(Deno.env.get("meta_scopes") || "");
const pemEncodedPrivateKey = Deno.env.get("private_key") || "";

const PRIVATE_KEY_HEADER = "-----BEGIN PRIVATE KEY-----";
const PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----";
const ims = "https://ims-na1.adobelogin.com";

const str2ab = (str: string) => {
  const buf = new ArrayBuffer(str.length);
  const bufView = new Uint8Array(buf);
  for (let i = 0, strLen = str.length; i < strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
};

const generateAdobeToken = async (): Promise<string | null> => {
  console.log(
    "*** Generating Authentication-Token for the Adobe-Target Api ***"
  );
  const rawBase64PrivateKey = pemEncodedPrivateKey
    .substring(
      PRIVATE_KEY_HEADER.length,
      pemEncodedPrivateKey.length - PRIVATE_KEY_FOOTER.length
    )
    .trim();
  const binaryDerString = atob(rawBase64PrivateKey);
  const binaryDer = str2ab(binaryDerString);

  const key = await crypto.subtle.importKey(
    "pkcs8",
    binaryDer,
    {
      name: "RSASSA-PKCS1-v1_5",
      hash: "SHA-256",
    },
    true,
    ["sign"]
  );

  const jwtPayload = {
    exp: Math.round(300 + Date.now() / 1000),
    iss: orgId,
    sub: technicalAccountId,
    aud: `${ims}/c/${clientId}`,
  };

  (jwtPayload as any)[`${ims}/s/${metaScopes[0]}`] = true;

  let token;
  try {
    token = await create({ alg: "RS256", typ: "JWT" }, jwtPayload, key);
  } catch (e) {
    console.error("Local token generation failed.");
    throw e;
  }

  const form = new FormData();
  form.append("client_id", clientId);
  form.append("client_secret", clientSecret);
  form.append("jwt_token", token);

  const postOptions = {
    method: "POST",
    body: form,
  };

  console.log("\n*** Retrieving auth-token from adobe ***");
  return fetch(`${ims}/ims/exchange/jwt/`, postOptions)
    .catch((e) => {
      console.error("Could not fetch token from adobe: Call failed!", e);
      throw e;
    })
    .then((res) => {
      return res.json().then((data) => {
        return {
          ok: res.ok,
          json: data,
        };
      });
    })
    .then(({ ok, json }) => {
      const { access_token, error, error_description } = json;
      if (ok && access_token) {
        return json.access_token;
      }

      if (error && error_description) {
        const swapError = new Error(error_description);
        (swapError as any).code = error;
        throw swapError;
      } else {
        console.error(
          `The response body is as follows: ${JSON.stringify(json)}`
        );
      }
      return null;
    });
};

export const requestAdobeApiToken = async () => {
  try {
    return await generateAdobeToken();
  } catch (e) {
    console.error("Requesting token failed!", e);
    return null;
  }
};

I took your code and simplified the key import and JWT signing using http://deno.land/x/jose.

import * as jose from "https://deno.land/x/jose/index.ts";

const key = await jose.importPKCS8(pemEncodedPrivateKey, "RS256");

const token = await new jose.SignJWT({
  [`${ims}/s/${metaScopes[0]}`]: true,
})
  .setProtectedHeader({ alg: "RS256" })
  .setIssuer(orgId)
  .setSubject(technicalAccountId)
  .setAudience(`${ims}/c/${clientId}`)
  .setIssuedAt()
  .setExpirationTime("5m")
  .sign(key);
Related