I want to let users to connect to my node.js server with their gmail. I have created a project in the Google Developers Console. Then use the following code:
const express = require('express');
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const credentials = require('../link/of/some/file.json'); // google credential
let clientSecret = credentials.web.client_secret;
let clientId = credentials.web.client_id;
let redirectUrl = credentials.web.redirect_uris[0];
const SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.readonly'
];
let auth = new googleAuth();
let oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
app.get('/connect', (req, res) => {
let authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
res.redirect(authUrl);
});
when /connect is called, it will redirect to google page to verify user. After user give access of his/her account, google will automatically call the following api with a code query parameter:
app.get('/auth/callback', (req, res) => {
// 'req.query.code' is provided by google
return oauth2Client.getToken(req.query.code, (err, token) => {
// token value:
/*
{
access_token: 'ya29.Gls2Ba_rXEA5EoBaFH5bPdDDzgaSWOtb0GSJcnaTP47Jh7HwHdF2ZJZOlQaCJBC5wjpq-sOLBVlIM9L8BMslVyHw22nveU0MwQ4iJPTq5vkjXDeitqtoYH8JO83w',
refresh_token: '1/S0C3w-8vnqcrGE4Z2mSW9ctYkaitVuZquBSJ0WJJHUs',
token_type: 'Bearer',
expiry_date: 1514897380473
}
*/
});
});
Now, My problem is, in the above token value, I cannot understand how can I get gmail address from that token. What am I missing???
Any Suggestion? Thanks in advance.
the first steps I would suggest is to store your token somewhere so you don't request it all the time.
Then when you recieve the token you put it in oauth2Client.credentials
oauth2Client.getToken(req.query.code, (err, token) => {
if (err) {
console.log(err);
return;
}
//TODO: Save token somewhere
oauth2Client.credentials = token;
});
Then you use the oauth2Client to do API calls
var gmail = google.gmail('v1');
gmail.users.labels.list({
auth: oauth2Client,
userId: 'me',
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var labels = response.labels;
if (labels.length == 0) {
console.log('No labels found.');
} else {
console.log('Labels:');
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
console.log('- %s', label.name);
}
}
});
Solved:
I have solve this problem, just calling getProfile, where userId is me. My code is given below:
const SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.compose'
];
app.get('/auth/callback', (req, res) => {
return oauth2Client.getToken(req.query.code, (err, token) => {
oauth2Client.credentials = token;
var gmail = google.gmail('v1');
gmail.users.getProfile({
auth: oauth2Client,
userId: "me",
}, function(error, response) {
console.log("getProfile : ", {error, response});
/*
output:
{
emailAddress: 'some.address#gmail.com',
messagesTotal: xxx,
threadsTotal: xxx,
historyId: 'xxxxx'
}
*/
});
});
});
Thanks to all of you.
Related
I've tried the soultion from this answer and it isn't working for me.
I've tried revoking access to my app and reauthorizing and it's not working either. Here is my auth code:
export function handleAuth() {
const oauth2Client = getOAuth2Client();
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: ['https://mail.google.com/'],
});
}
I take the URL returned from this and use it to auth my gmail account. Then I have the auth callback:
app.get('/oauth2callback', async (req, res) => {
const query = req.query;
const code = query.code as string;
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials({
refresh_token: tokens.refresh_token,
});
});
And I have a listener waiting for new tokens:
oauth2Client.on('tokens', async (tokens) => {
if (tokens.refresh_token) {
oauth2Client.setCredentials({
refresh_token: tokens.refresh_token,
});
if (tokens.access_token && tokens.refresh_token) {
const tokenRepo = getCustomRepository(GcpTokenRepository);
await tokenRepo.create({
log in my db...
});
}
}
});
Then when I try and run the watch method so I can listen to emails:
async watch() {
const gmail = await this.authGmail(); // method that returns type Promise<gmail_v1.Gmail>
const res = await gmail.users.watch({
userId: 'me',
requestBody: {
labelIds: ['INBOX'],
topicName: `topic name`,
},
});
console.log('👀 Watch re-initialized!', res);
}
And this watch method throws the error: Error: No access, refresh token, API key or refresh handler callback is set
When I console log my auth variable returned from google.auth.OAuth2() I also notice the credentials field is an empty object...
Am I missing anything here?
Where is your callback() ?
app.get('/oauth2callback', async (req, res) => {
const query = req.query;
const code = query.code as string;
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials({
refresh_token: tokens.refresh_token,
});
});
I'm new to website building. I am using node js, express, and express-handlebars. I have 3 hbs page file called signup, verify, and login. I am trying to check if signup page has any errors using exports.signup and then if it's alright then rendering verify page and authenticating it using otp. Now my problem is I need to enter signup page values from verify page in the database after user is verified. How can I get signup page values from exports.signup and use it in exports.verify function?
This works to check in signup page:
exports.signup = (req, res) => { console.log(req.body);
const { name, email, password, passwordConfirm } = req.body;
db.query("select email from test where email=?",[email],async (error, results) => {
if (error) {
console.log(error);
}
if (results.length > 0) {
return res.render("signup", {
message: "The email is already in use",
});
} else if (password !== passwordConfirm) {
return res.render("signup", {
message: "Passwords do not match",
});
}
let hashedPassword = await bcrypt.hash(password, 8);
console.log(hashedPassword);
var digits = "0123456789";
let OTP = "";
for (let i = 0; i < 6; i++) {
OTP += digits[Math.floor(Math.random() * 10)];
}
let transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.GMAIL,
pass: process.env.GMAIL_PASSWORD,
},
});
let mailOptions = {
from: "checkmate.sdp#gmail.com",
to: email,
subject: "Verification code for Checkmate profile.",
text: "Your OTP is : " + OTP,
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log("Email sent: " + info.response);
res.render("verify");
}
});
}
);
};
This verifies the user and enters values in database: (I haven't added anything here yet)
exports.verify = (req, res) => {
console.log(req.body);
};
Just an overview for you
signup.js
'use-strict';
exports.signup = (params) => { console.log("Hit", params) }
controller.js
'use-strict';
var signup = require('./signup');
var http = require('http');
// you can now access the signup function,
signup.signup({username: 'test', password: 'test'})
Looks like you want this to be an HTTP endpoint reciever,
depending on what library youre using, example with Koa route
Backend--
signup.js
var route = require('koa-route');
exports.init = (app) => {
app.use(route.post('/signup', signup));
}
async function signup(ctx) {
var body = ctx.request.body;
//operate
}
Frontend --
$.ajax({
url: "/signup",
type: "post",
data: JSON.stringify({username: 'get from html', password: 'get from html'})
});
I'm trying to get the tokens of user's to integrate one drive in an APP that's I'm building
I first get the auth URL, here's the endpoint implementation
const pca = new msal.ConfidentialClientApplication(config);
exports.getAuthUrl = (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read', 'files.readwrite', 'offline_access'],
redirectUri: 'https://localhost:8080/onedrive',
};
// get url to sign user in and consent to scopes needed for application
pca
.getAuthCodeUrl(authCodeUrlParameters)
.then((response) => {
res.status(200).send(response);
})
.catch((error) => console.log(JSON.stringify(error)));
};
Then using the code that I got back after the client auth is successful is passed as a parameter in a second endpoint to obtain the tokens
exports.getToken = (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ['user.read', 'files.readwrite', 'offline_access'],
redirectUri: 'https://localhost:8080/onedrive',
};
pca
.acquireTokenByCode(tokenRequest)
.then((response) => {
console.log('\nResponse: \n:', response);
res.status(200).send(response);
})
.catch((error) => {
console.log(error);
res.status(500).send(error);
});
};
So as mentioned in the official doc if you add the offline_access scope you get back a refresh token
does anyone have any experience with this ? I used these two libraries that part of the code is already provided by Microsoft const msal = require('#azure/msal-node');
Here is How to get the Refresh and Access token..
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const express = require("express");
const msal = require('#azure/msal-node');
const SERVER_PORT = process.env.PORT || 3000;
const REDIRECT_URI = "http://localhost:3000/redirect";
// Before running the sample, you will need to replace the values in the config,
// including the clientSecret
const config = {
auth: {
clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
authority: "https://login.microsoftonline.com/84fb56d3-e15d-4ae1-acd7-cbf83c4c0af3",
clientSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
app.get('/', (req, res) => {
const authCodeUrlParameters = {
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
prompt:'consent'
};
// get url to sign user in and consent to scopes needed for application
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
res.redirect(response);
}).catch((error) => console.log(JSON.stringify(error)));
});
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
accessType: 'offline',
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
const accessToken = response.accessToken;
const refreshToken = () => {
const tokenCache = pca.getTokenCache().serialize();
const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken
const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
return refreshToken;
}
const tokens = {
accessToken,
refreshToken:refreshToken()
}
console.log(tokens)
res.sendStatus(200);
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
});
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
Well, after a few days of research I found out that msal-node does not expose the refresh token to the end-user by design. It is stored and used internally under the hood when you need a new access token. You should call acquireTokenSilent each time you need an access token and msal-node will manage the tokens by either returning a cached token to you or using the refresh token to acquire a new access token.
So what I did is actually make the call directly to the endpoint provided by Microsoft
You should first get the code using the URL provided in this doc [https://learn.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/msa-oauth?view=odsp-graph-online][1]
the take the code and pass it in the next enpoint which is the following
exports.getToken = async (req, res) => {
request.post(
'https://login.live.com/oauth20_token.srf',
{
form: {
code: req.body.code,
client_id: 'CLIENT-ID',
redirect_uri: 'REDIRECT-URI',
client_secret: 'CLIENT-SECRET',
grant_type: 'authorization_code',
},
},
async function (err, httpResponse, body) {
if (err) {
res.status(500).send({ Message: err.message });
} else {
let response = JSON.parse(body);
res.status(200).send({ Body: JSON.parse(body) });
}
}
);
};
and that's how I made it work, using msal there was no way
Okay I am trying on gmail API ? It is driving me crazy, I am kind of new too it. First of all I want to print JSON response of Email and Labels to Browser or postman. Second Everytime I call API i have to reauthenticate.
any help would be really appreacited. what can I do in order to get to print response in browser which I am getting in console.
const { google } = require('googleapis');
const express = require('express');
const OAuth2Data = require('./credentials.json');
const fs = require('fs');
const { response } = require('express');
const app = express()
const TOKEN_PATH = 'token.json';
const CLIENT_ID = OAuth2Data.client.id;
const CLIENT_SECRET = OAuth2Data.client.secret;
const REDIRECT_URL = OAuth2Data.client.redirect_uris;
const oAuth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL)
var authed = false;
app.get('/', (req, res) => {
if (!authed) {
// Generate an OAuth URL and redirect there
const url = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://mail.google.com/'
});
console.log(url)
res.redirect(url);
} else {
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
gmail.users.labels.list({
userId: 'me',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const labels = res.data.labels;
if (labels.length) {
console.log('Labels:');
labels.forEach((label) => {
console.log(`- ${label.name}`);
});
} else {
console.log('No labels found.');
}
});
res.send('Logged in')
}
})
app.get('/auth/google/callback', function (req, res) {
const code = req.query.code
if (code) {
// Get an access token based on our OAuth code
oAuth2Client.getToken(code, function (err, token) {
if (err) {
console.log('Error authenticating')
console.log(err);
} else {
console.log('Successfully authenticated');
oAuth2Client.setCredentials(token);
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
authed = true;
res.redirect('/')
}
});
}
});
const port = process.env.port || 5000
app.listen(port, () => console.log(`Server running at ${port}`));
app.get('/m',(req, res) => {
if (!authed) {
// Generate an OAuth URL and redirect there
const url = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://mail.google.com/'
});
console.log(url)
res.redirect(url);
}
else {
const gmail1 = google.gmail({ version: 'v1', auth: oAuth2Client });
gmail1.users.messages.list({userId: 'me', maxResults: 2}, function (err, res) {
if (err) {
console.log('The API returned an error 1: ' + err);
return;
}
// Get the message id which we will need to retreive tha actual message next.
for (let index = 0; index < 2; index++) {
var message_id = res['data']['messages'][index]['id'];
gmail1.users.messages.get({auth:oAuth2Client, userId: 'me', 'id': message_id}, function(err, res) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
console.log('data',res['data']);
});
}
// Retreive the actual message using the message id
});
}
})
I am trying to create a client-side script that allows someone to login to their Google Account and then access a Firestore database once they are authenticated. Everything works except for some reason after signing in using firebase.auth it isn't passing this data to firebase.firestore to say they are authenticated.
Here is the script that I am using, the only part that is failing is when I am trying to add to a Firestore collection.
const firebase = require ("firebase/app");
require("firebase/auth");
require("firebase/firestore");
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
const util = require('util')
var config = {
**HIDDEN**
};
firebase.initializeApp(config);
const SCOPES = ['email','profile'];
const TOKEN_PATH = 'token.json';
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Gmail API.
authorize(JSON.parse(content), listLabels);
});
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function listLabels(auth) {
var credential = firebase.auth.GoogleAuthProvider.credential(auth.credentials.id_token);
firebase.auth().signInAndRetrieveDataWithCredential(credential).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
var email = error.email;
var credential = error.credential;
console.log(errorCode);
console.log(errorMessage);
});
var firestore = firebase.firestore();
firestore.settings({
timestampsInSnapshots: true
});
firestore.collection("users").add({
test: "Hello"
});
firebase.auth().signOut();
}
And below is my rules for the database, if I change it to just if true; it works perfectly fine. But I want to make sure only an authenticated user can access that database.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
The method signInAndRetrieveDataWithCredential returns a promise, and you aren't waiting for it to finish before accessing the data on Firestore, so that is probably what is causing the issues. I believe this might work:
function listLabels(auth) {
var credential = firebase.auth.GoogleAuthProvider.credential(auth.credentials.id_token);
firebase.auth().signInAndRetrieveDataWithCredential(credential)
.then(user => {
var firestore = firebase.firestore();
firestore.settings({
timestampsInSnapshots: true
});
firestore.collection("users").add({
test: "Hello"
});
firebase.auth().signOut();
})
.catch(function (error) {
var errorCode = error.code;
var errorMessage = error.message;
var email = error.email;
var credential = error.credential;
console.log(errorCode);
console.log(errorMessage);
});
}