My backend generates the access_token with below redirect controller:
const logged = async (req: Request, res: Response) => {
const code = (req.query.code as string) || null;
const state = (req.query.state as string) || null;
const error = (req.query.error as string) || null;
const oauth2Client = new youtube.auth.OAuth2(
GOOGLE_CLIENT_ID,
GOOGLE_SECRET,
GOOGLE_REDIRECT_URI,
);
const { tokens: { access_token }} = await oauth2Client.getToken(code);
res.send({ access_token });
};
Returned code is similar to: "ya29.<MUCH_MORE_CHARACTERS_HERE>_BtA0163"
Then somewhere else I'm trying to create a client with this access token like this:
const youtube = require("#googleapis/youtube");
const youtubeApi = youtube.youtube({
version: "v3",
auth: "ya29.<MUCH_MORE_CHARACTERS_HERE>_BtA0163",
});
(Btw I'm aware that I could get the client on the redirect controller. I just don't want to)
But everytime I try to access something, it gives me error:
code: 400,
errors: [
{
message: 'API key not valid. Please pass a valid API key.',
domain: 'global',
reason: 'badRequest'
}
]
I believe your goal is as follows.
You want to use your access token to the client with googleapis for Node.js.
In your script, how about the following modification?
From:
const youtube = require("#googleapis/youtube");
const youtubeApi = youtube.youtube({
version: "v3",
auth: "ya29.<MUCH_MORE_CHARACTERS_HERE>_BtA0163",
});
To:
const { google } = require("googleapis");
const auth = new google.auth.OAuth2();
auth.setCredentials({ access_token: "ya29.<MUCH_MORE_CHARACTERS_HERE>_BtA0163" });
const youtubeApi = google.youtube({ version: "v3", auth });
Related
Google shuts down "Less secure app" feature on their mail services last month. Because of this, I cannot just use Nodemailer by using email and password only. I am forced to setup Nodemailer with Google OAuth. But, the refresh token expires every week. What should I do?
Here's my transporter code.
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: process.env.GA_MAIL,
accessToken,
clientId: process.env.GA_CLIENT_ID,
clientSecret: process.env.GA_CLIENT_SECRET,
refreshToken: process.env.GA_REFRESH_TOKEN,
},
});
This is the problem better worded :
This is the file token.js :
const { google } = require('googleapis');
const path = require('path');
const fs = require('fs');
const credentials = require('./email.json');
// Replace with the code you received from Google
const code = "";
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
oAuth2Client.getToken(code).then(({ tokens }) => {
const tokenPath = path.join(__dirname, 'token.json');
fs.writeFileSync(tokenPath, JSON.stringify(tokens));
console.log('Access token and refresh token stored to token.json');
});
when running the token.js file a file called token.json gets created with some access tokens :
{"access_token":"","refresh_token":"","scope":"https://www.googleapis.com/auth/gmail.send","token_type":"Bearer","expiry_date":1656595108942}
but after 7 days the access_token expires.
A solution I came up with , though it's untested , is to have inside a .catch() block a call for a token refresh like so :
main.js :
//...
const refreshToken = require("./refresh_token.js");
//...
}).catch((err) => {
reply(interaction,"Error, Refreshing token.. Please try again");
console.error(err);
refreshToken();
return;
});
//...
refresh_token.js :
const { google } = require('googleapis');
const path = require('path');
const fs = require('fs');
const credentials = require('./email.json');
const token = require("./token.json");
module.exports = () => {
const code = token.refresh_token;
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
oAuth2Client.getToken(code).then(({ tokens }) => {
const tokenPath = path.join(__dirname, 'token.json');
fs.writeFileSync(tokenPath, JSON.stringify(tokens));
console.log('Access token and refresh token stored to token.json');
});
}
The reason I put it in a catch statement is because when the token is not expired we get a invalid_grant error when trying to run oAuth2Client.getToken(token.refresh_token)
^^ THIS HAS NOT BEEN TESTED ^^
if the refresh_token expires as well then I have no idea how to do this , please help
Setting the testing mode into a production mode in Google Developer Console will prevent refresh token from exipiring.
The react-google-login from the client react app sends the response back to the Nodejs server with a post request-
client code -
import axios from 'axios';
import React, { Component } from 'react';
import GoogleLogin from 'react-google-login';
import refreshTokenSetup from '../../utils/refreshToken';
const clientId =
'xxxxxx-xfdgsdjg3gfxxxxxxxxxxx.apps.googleusercontent.com';
function Login() {
const onSuccess = (res) => {
console.log('Login Success: currentUser:', res.profileObj);
alert(
`Logged in successfully welcome ${res.profileObj.name} 😍. \n See console for full profile object.`
);
axios
.post('http://localhost:5000/auth/checkToken', { body: res.tokenId })
.then()
.catch((err) => {
console.log(err);
});
};
const onFailure = (res) => {
console.log('Login failed: res:', res);
alert(
`Failed to login. 😢 Please ping this to repo owner twitter.com/sivanesh_fiz`
);
};
return (
<div>
<GoogleLogin
clientId={clientId}
buttonText='Login'
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
style={{ marginTop: '100px' }}
isSignedIn={true}
/>
</div>
);
}
export default Login;
the backend route-
const { OAuth2Client } = require('google-auth-library');
const key = require('../config/key');
module.exports = {
checkToken: (req, res, next) => {
console.log('checking begins...', req.body);
const client = new OAuth2Client(key.GOOGLE_CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: req.body,
audience: key.GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
},
};
The above code is in reference to official Google Documentation available at- https://developers.google.com/identity/sign-in/web/backend-auth
Now everything works fine, user is signed in in the client side, the tokenId is sent back to the server and can be verified by console logging it, even on https://jwt.io/ but the following error is shown-
TypeError: jwt.split is not a function
at OAuth2Client.verifySignedJwtWithCertsAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:528:30)
at OAuth2Client.verifyIdTokenAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:394:34)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async verify (E:\Projects\EAbackend\middleware\auth.js:9:22)
The main issue is that the example from Google doesn't really tell us what is expected as input to the verifyIdToken({options}) function.
This is what Google stated:
After Google returns an ID token, it's submitted by an HTTP POST method request, with the parameter name credential, to your login endpoint.
Which to me, is a little unclear of what is actually sent to the server to be verified. So here is a working example, copied/pasted from https://developers.google.com/identity/gsi/web/guides/verify-google-id-token, but with proper definitions for token and CLIENT_ID that are not mentioned on the Google site.
Server side Node JS:
exports.googleTokenChecker = (request, response) => {
const CLIENT_ID = request.body.clientId;
const token = request.body.credential;
// copied from Google example
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
}
Client side HTML to show what is sent to the backend:
<div id="g_id_onload"
data-client_id="CLIENT_ID.apps.googleusercontent.com"
data-callback="handleCredentialResponse"
data-auto_prompt="false">
</div>
<script>
function handleCredentialResponse(response) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:3000/api/google_token_checker", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(response));
}
</script>
// contents of response parameter
// {
// clientId: 'CLIENT_ID.apps.googleusercontent.com',
// credential: 'JWT_HEADER.JWT_PAYLOAD.JWT_SIGNATURE',
// select_by: 'btn'
// }
Problem is in the idToken: req.body,
req.body has a body object in which the token was present, simply changing it to req.body.body solved the error.
The problem might be very begginner level but took a lot of my time and no online resourse was available which could point me in any direction.
Check the POST Request you will find the error.
I need to call a Dialogflow V2 APIs, but to do this i need to generate a token.
I found many codes about how to do this, but doesn't work for me.
I'm doing an API to generate token and then pass the token to another API to call DialogFlow APIs.
Can anyone help me how to generate a token to call Dialogflow V2 APIs?
My API to generate token Code below:
const express = require('express');
const router = express.Router();
const googleAuth = require('google-oauth-jwt');
function generateAccessToken() {
return new Promise((resolve) => {
googleAuth.authenticate(
{
email: "<myemail>",
key: "<myprivatekey>",
scopes: "https://www.googleapis.com/auth2/v4/token",
},
(err, token) => {
resolve(token);
},
);
});
}
router.get('/token', async(req, res) => {
try {
let tok = await generateAccessToken();
return res.status(200).send({ Token: tok});
} catch(err) {
return res.status(500).send({ error: 'Erro ao gerar token'});
}
});
module.exports = app => app.use('/', router);
Dialogflow V2 no longer uses developer/client access tokens. You need to use your service account key file to access the API. You can follow this article to set up your service account. Once you have set up your service account, you can check the sample implementation of dialogflow using nodejs.
Install client library:
npm install #google-cloud/dialogflow
Code snippet from the github link:
const dialogflow = require('#google-cloud/dialogflow');
const uuid = require('uuid');
/**
* Send a query to the dialogflow agent, and return the query result.
* #param {string} projectId The project to be used
*/
async function runSample(projectId = 'your-project-id') {
// A unique identifier for the given session
const sessionId = uuid.v4();
// Create a new session
const sessionClient = new dialogflow.SessionsClient();
const sessionPath = sessionClient.projectAgentSessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: 'hello',
// The language used by the client (en-US)
languageCode: 'en-US',
},
},
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
console.log('Detected intent');
const result = responses[0].queryResult;
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
console.log(` Intent: ${result.intent.displayName}`);
} else {
console.log(` No intent matched.`);
}
}
Below the code worked for me returning Bearer Token:
const express = require('express');
const router = express.Router();
const googleAuth = require('google-oauth-jwt');
const {google} = require('googleapis');
const request = require('request');
async function generateAccessToken2() {
const jwtClient = new google.auth.JWT(
"<email serviceaccount>",
null,
"<Private Key>",["https://www.googleapis.com/auth/indexing","https://www.googleapis.com/auth/cloud-platform","https://www.googleapis.com/auth/dialogflow"],
null
);
let tok = "";
tok = jwtClient.authorize();
return tok;
};
I have created multiple website using this tutorial https://medium.com/#orels1/using-discord-oauth2-a-simple-guide-and-an-example-nodejs-app-71a9e032770 and they all worked perfectly until yesterday. The problem is that when a user authenticates and goes to https://mywebsite.com/api/discord/callback?code={discord response code} the api gets an error when trying to convert the code with a access_token.
This is the code for my discord api callback:
const router = express.Router();
const fetch = require('node-fetch');
const FormData = require('form-data');
const btoa = require('btoa');
const { catchAsync } = require(__dirname+'/utils');
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;
const redirect = encodeURIComponent('https://mywebsite.com/api/discord/callback');
router.get('/login', (req, res) => {
res.redirect(`https://discordapp.com/api/oauth2/authorize?client_id=${CLIENT_ID}&scope=identify&response_type=code&redirect_uri=${redirect}`);
});
router.get('/callback', catchAsync(async (req, res) => {
if (!req.query.code) throw new Error('NoCodeProvided');
const code = req.query.code;
const creds = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
const response = await fetch(`https://discord.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirect}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${creds}`,
},
});
const json = await response.json();
console.log(json)
res.redirect(`/finallogin/?token=${json.access_token}`);
}));
module.exports = router;
When a user authenticates, he gets redirected to https://mywebsite.com/finallogin/?token=undefined and I'm getting this error on console:
error: 'invalid_request',
error_description: 'Missing "code" in request.'
}
This was working perfectly before and just randomly broke a few days ago on all of my websites. Any help / solution / idea will be appreciated.
(Note: the "https://mywebsite.com" is just an example and not my actual website url)
Thanks,
RealPower2Maps.
I'm implementing Slack webhook authentication with Firebase Cloud Function. However, the app is redirected to /api/auth/slack/undefined after choosing the channel I get notified. It should be redirected to /api/auth/slack/callback.
Error: could not handle the request (/api/auth/slack/undefined)
Slack app setup done:
The Frontend code:
const slackAuthorizeURL= (uid) =>
`https://slack.com/oauth/v2/authorize?client_id=${process.env.REACT_APP_SLACK_CLIENT_ID}&scope='incoming-webhook'&redirect_uri=${process.env.REACT_APP_CLOUD_FUNCTIONS_ORIGIN}/api/auth/slack/callback&uid=${uid}&redirectTo=${window.location.href}`
<a href={slackAuthorizeURL('uid')}>Add to Slack</a>
The Server code:
const { WebClient } = require('#slack/web-api')
app.get('/auth/slack/callback', (req, res) => {
handleSlackUserInfo(req)
res.redirect(req.session.redirectTo)
})
const handleSlackUserInfo = async (req) => {
const { code, uid, redirectTo } = req.session
const result = await new WebClient().oauth.v2.access({
client_id: config.slack.client_id,
client_secret: config.slack.client_secret,
code,
})
console.log("result", result)
}
I added uid=${uid}&redirectTo=${window.location.href} after callback url. Is this wrong too?