I'm trying to get an access token so I can use the Google Cloud TTS.
This looks the only way I can do it. I'm using Cloudflare workers and because this needs to run on a browser, some of libraries that I think would facilitate the job I can't use.
I'm in an internship, the guy who gave me this task sent me this file.
{
"type": "service_account",
"project_id": "...",
"private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\nMII...NF0=\n-----END PRIVATE KEY-----\n",
"client_email": "...",
"client_id": "...",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "..."
}
I already could create an audio using this file by putting the path of the file as an specific environment variable and then using the gcloud
CLI tool to print the access token, then I used it. But I didn't know the token changed.
Now I'm trying to use JWT tokens.
This is a file that I created to test the JWT.
It creates the JWT but when I do the POST request to get the access_token
in the Response I just get the id_token
.
const jwt = require("jsonwebtoken");
const credentials = require("./credentials.json");
const corsHeaders = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, HEAD, POST, OPTIONS",
"Access-Control-Allow-Headers": "Authorization",
"Access-Control-Allow-Credentials" : true,
}
async function main() {
const JWTToken = jwt.sign({
"iss": credentials.client_email,
"sub": credentials.client_email,
"scope": "https://texttospeech.googleapis.com/$discovery/rest?version=v1",
//"aud": credentials.token_uri,
"aud": "https://www.googleapis.com/oauth2/v4/token",
"exp": Math.floor( new Date() / 1000) 60 * 45,
"iat": Math.floor( new Date() / 1000),
}, credentials.private_key, {
"algorithm": "RS256",
"header": {
"kid": credentials.private_key_id,
"typ": "JWT",
"alg": "RS256",
}
});
const JWTBody = new URLSearchParams();
JWTBody.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
JWTBody.append("assertion", JWTToken);
let JWTResponse;
try {
//JWTResponse = await fetch("https://oauth2.googleapis.com/token", {
JWTResponse = await fetch("https://www.googleapis.com/oauth2/v4/token", {
method: "POST",
body: JWTBody,
});
} catch (e) {
console.error(e);
return new Response(`${e.name}: ${e.message}`, {
status: 500,
statusText: "Internal Server Error",
headers: new Headers(corsHeaders),
});
}
let JWTResponseBody;
if (JWTResponse.ok) {
JWTResponseBody = await JWTResponse.json();
console.log("JWT", JWTResponseBody);
console.log("JWT ACCESS_TOKEN", JWTResponseBody["access_token"]);
} else {
console.error("HTTP status code: ", JWTResponse.status, JWTResponse.statusText, JWTResponse);
return new Response("HTTP status code: " JWTResponse.status JWTResponse.statusText, {
status: 500,
statusText: "Internal Server Error",
headers: new Headers(corsHeaders),
});
}
const TTSGoogleAPIsEndpoint = new URL("https://texttospeech.googleapis.com");
const TTSRESTResources = {
synthesize: new URL("/v1/text:synthesize", TTSGoogleAPIsEndpoint),
list: new URL("/v1/voices", TTSGoogleAPIsEndpoint),
};
let response;
try {
response = await fetch(TTSRESTResources.synthesize, {
method: "POST",
headers: new Headers({
"Authorization": `Bearer ${JWTResponseBody["access_token"]}`,
"Content-Type": "application/json; charset=utf-8",
}),
body: JSON.stringify({
"audioConfig": {
"audioEncoding": "LINEAR16",
"pitch": 0,
"speakingRate": 1
},
"input": {
"ssml": "<speak> <emphasis level=\"strong\">To be</emphasis> <break time=\"200ms\"/> or not to be? </speak>"
},
"voice": {
"languageCode": "en-US",
"name": "en-US-Standard-A"
}
}),
});
} catch (e) {
console.error(e);
return new Response(`${e.name}: ${e.message}`, {
status: 500,
statusText: "Internal Server Error",
headers: new Headers(corsHeaders),
});
}
if (response.ok) {
const audio = await response.json();
console.log(audio);
} else {
console.error("HTTP status code: ", response.status, response.statusText);
console.log(response.headers.get("WWW-Authenticate"));
return new Response("HTTP status code: " response.status response.statusText, {
status: 500,
statusText: "Internal Server Error",
headers: new Headers(corsHeaders),
});
}
}
main();
When I run the code it prints this:
(node:53185) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
JWT {
id_token: 'eyJh ... THE ID_TOKEN ... BSZw'
}
JWT ACCESS_TOKEN undefined
HTTP status code: 401 Unauthorized
Bearer realm="https://accounts.google.com/", error="invalid_token"
I've console logged the JWTToken
already and did the POST Request on Postman. It gives the same Response, only the id_token
.
I think the problem can be the JWT payload content, I don't know if the scope it's right neither if I need to use the https://www.googleapis.com/oauth2/v4/token
URL or the one in the credentials for the aud
. The same about the URL that I need to use for the fetch()
. But I think I tested all the URL possibilities and it never gives the access_token
.
Thank you
Solution
The scope on the JWT payload was wrong. It needs to be "scope": "https://www.googleapis.com/auth/cloud-platform",
.
Answered by John Hanley.
CodePudding user response:
Your code is specifying an OAuth scope that does not exist (and the format is entirely wrong).
async function main() {
const JWTToken = jwt.sign({
"iss": credentials.client_email,
"sub": credentials.client_email,
"scope": "https://www.googleapis.com/auth/cloud-platform",
...
The URI https://www.googleapis.com/oauth2/v4/token
is correct. You can also use the value from auth_uri
: https://accounts.google.com/o/oauth2/auth
.
That should solve the problem with the JWT Access Token. If you have further problems with the Text To Speech API, post a new question.