'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 |