'HERE REST API - React/Typescript - OAuth Signature mismatch

I am trying to implement the OAuth authentication for HERE REST API following this documentation: https://developer.here.com/documentation/identity-access-management/dev_guide/topics/sdk.html

My problem is that when I send the POST request with the OAuth Header and Body, the response I receive from the server is: Signature mismatch. Authorization signature or client credential is wrong.

I probed my credentials using the OAuth1.0 authorization in Postman and the HERE API responds with a valid access token. Hence my credentials values are correct.

In addition to the official documentation, I followed recommandations published in various Stack Overflow answers, especially the encoding of all parameters (encodeURI).

I suspect the CryptoJS npm package to be the source of my problem. The result of the HmacSHA256 cryptographic function is very different from various online HmaSHA256 that I tried with my baseString as parameter. However, the crypto-js library (+ @type/crypto-js for Typescript) seems to be the reference for React Typescript crypto functions. I couldn't figure out where I got it wrong :/

Following is my implementation of the OAuth method to get my access token. I am using :

    "@types/crypto-js": "^4.1.1",
    "crypto-js": "^4.1.1",

     <node.version>v14.14.0</node.version>
     <npm.version>v6.14.8</npm.version>

     HERE_ACCESS_KEY = <here.access.key>;
     HERE_ACCESS_SECRET = <here.access.key.secret>;
     HERE_AUTH_URL = "https://account.api.here.com/oauth2/token";

My frontend code:

   import CryptoJS from "crypto-js";


   private buildOAuthSignatureBaseString(accessKey: string, nonce: string, hashMethod: string, timestamp: string) {
        const baseStringParameters = `grant_type=client_credentials&oauth_consumer_key=${accessKey}&oauth_nonce=${nonce}&oauth_signature_method=${hashMethod}&oauth_timestamp=${timestamp}&oauth_version=1.0`;
        return `POST&${encodeURIComponent(this.HERE_AUTH_URL)}&${encodeURIComponent(baseStringParameters)}`;
    }

    private buildHereOauthSignature(accessKey: string, nonce: string, hashMethod: string, timestamp: string) {
        const baseString = this.buildOAuthSignatureBaseString(accessKey, nonce, hashMethod, timestamp);
        const signingKey = `${encodeURIComponent(this.HERE_ACCESS_SECRET)}&`;
        const hashedBaseString = CryptoJS.HmacSHA256(baseString, signingKey);
        const base64hash = CryptoJS.enc.Base64.stringify(hashedBaseString);
        return encodeURIComponent(base64hash);
    }

    public getHereMapsApiAccessToken() {
        const hashMethod = encodeURIComponent("HMAC-SHA256");
        const accessKey = encodeURIComponent(this.HERE_ACCESS_KEY);
        const nonce = encodeURIComponent(new Buffer(random(8)).toString("base64"));
        const timestamp = Math.floor(new Date().getTime() / 1000).toString(10);
        const signature = this.buildHereOauthSignature(accessKey, nonce, hashMethod, timestamp);
        const headers = new Headers({
            "Content-Type": "application/x-www-form-urlencoded",
            "Authorization": `OAuth oauth_consumer_key="${accessKey}", oauth_nonce="${nonce}", oauth_signature_method="${hashMethod}", oauth_timestamp="${timestamp}", oauth_version="1.0", oauth_signature="${signature}"`
        });
        const method = HTTP_METHODS.POST;
        const options = {headers, method, body: JSON.stringify({grant_type: "client_credentials"})};

        return fetch("https://account.api.here.com/oauth2/token", options)
            .then((response) => {
                console.log(response);
                return true;
            }).catch((error) => {
                console.log(error);
                return false;
            });
    }

Thanks in advance !



Solution 1:[1]

You can try it from this example https://github.com/alexisad/reactjs_oauth2

Main code is in src/App.jsx:

/**
 * Gets an access token for a given access key and secret.
 * @param {*} access_key 
 * @param {*} access_secret 
 */
 export const getToken = (access_key, access_secret) => {
  let url = "https://account.api.here.com/oauth2/token";
  let key = encodeURI(access_key);
  let secret = encodeURI(access_secret);
  let nonce = btoa(Math.random().toString(36)).substring(2, 13);
  let timestamp = Math.floor(Date.now()/1000);
  let normalizedUrl = encodeURIComponent(url);
  let signing_method = encodeURI("HMAC-SHA256");
  let sig_string = "oauth_consumer_key="
  .concat(key)
  .concat("&oauth_nonce=")
  .concat(nonce)
  .concat("&oauth_signature_method=")
  .concat(signing_method)
  .concat("&oauth_timestamp=")
  .concat(timestamp)
  .concat("&").concat("oauth_version=1.0");

  let normalised_string = "POST&".concat(normalizedUrl).concat("&").concat(encodeURIComponent(sig_string));
  let signingKey = secret.concat("&");

  let digest = CryptoJS.HmacSHA256(normalised_string, signingKey);
  let signature = CryptoJS.enc.Base64.stringify(digest);

  let auth = 'OAuth oauth_consumer_key="'
  .concat(key)
  .concat('",oauth_signature_method="')
  .concat(signing_method)
  .concat('",oauth_signature="')
  .concat(encodeURIComponent(signature))
  .concat('",oauth_timestamp="')
  .concat(timestamp)
  .concat('",oauth_nonce="')
  .concat(nonce)
  .concat('",oauth_version="1.0"')

  return axios({
      method: 'post',
      url: url,
      data: JSON.stringify({grantType: "client_credentials"}),
      headers: {
          'Content-Type': "application/json",
          'Authorization': auth
      }
  });
}

/**
 * Send request for a given token and url.
 * @param {*} token 
 * @param {*} url
 */
export const sendReqOauth2 = (token, url) => { 
  return axios({
    method: 'get',
    url: url,
    headers: {
        'Authorization': 'Bearer ' + token
    }
  });
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 HERE Developer Support