Salesforce Authentication in NodeJs - oauth-2.0

I am trying to authenticate Salesforce to NodeJS application.I getting error Like Error: invalid_grant - authentication failure .What else I am missing here. Do I need to do any configurations from salesforce side. Here is my code. Could Someone help me on this?
app.js
var nforce = require('nforce');
const client_id = '**'
const client_secret = '****'
const redirect_uri = 'https://***.sandbox.my.salesforce.com/services/oauth2/success'
const sfdc_user = '*****'
const sfdc_pass = '***'
const credentials = {
client_id :client_id,
client_secret:client_secret,
grant_type:"password",
username:sfdc_user,
password:sfdc_pass
}
async function getConnection(){
const loginUrl = "https://***.sandbox.my.salesforce.com/services/oauth2/token";
var org = nforce.createConnection({
clientId: credentials.client_id,
clientSecret: credentials.client_secret,
redirectUri: redirect_uri,
});
console.log('org >>'+JSON.stringify(org));
let oauth= await org.authenticate({ username: credentials.username, password: credentials.password});
console.log('oauth >>'+oauth); //Couldnt get this console
const access_token = oauth.access_token;
const sf_auth_url = oauth.instance_url + '/services/data/v48.0/'
sf_auth = {
'Authorization':'Bearer ' + access_token,
'Content-type': 'application/json',
'Accept-Encoding': 'gzip'
}
return { sf_auth,sf_auth_url }
}
module.exports = { getConnection }
main.js
const f = require('./app');
const https = require('https')
const fs = require('fs')
const port = 3000
const server = https.createServer(function(req,res){
res.writeHead(200,{'Content-Type': 'text/html'})
res.end();
})
server.listen(port,function(error){
if(error){
console.log('Something Went Wrong!')
}else{
console.log('Server is listening on port '+port)
f.getConnection();
}
})

Usually when I have received this error it's due to not having the user's security security token appended to the end of the password field. This is the token sent to you via email when you first set your password or performed your last password reset. If you need to reset it you can do so via Personal Settings > Reset My Security Token
Reset Your Security Token
In the event it's not that, this username/password authentication documentation should help.
OAuth 2.0 Username-Password Flow for Special Scenarios

Related

Getting 400 error code from Axios request while implementing token revocation flow for Apple Sign-In in Firebase

I am trying to implement the token revocation flow for Apple Sign-In in Firebase, following the instructions given in this GitHub repository: https://github.com/jooyoungho/apple-token-revoke-in-firebase. However, when I try to run the code to get the Refresh Token, I keep receiving a 400 error code from the Axios request.
Here's the code I'm trying to implement:
exports.getRefreshToken = functions.https.onRequest(async (request, response) => {
const axios = require('axios');
const qs = require('qs')
const code = request.query.code;
const client_secret = makeJWT();
let data = {
'code': code,
'client_id': clientId,
'client_secret': client_secret,
'grant_type': 'authorization_code'
}
return axios.post(`https://appleid.apple.com/auth/token`, qs.stringify(data), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
})
.then(async res => {
const refresh_token = res.data.refresh_token;
response.send(refresh_token);
})
.catch((error) => {
response.send(error);
functions.logger.log('Error getting refresh token', {error})
})
});
I am using the app bundleID for client_id
The authorizationCode in my app is formatted in this manner
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.x.xxxxx.xxxxxxxxxxxxxxxxxxxxxx
I am using #awesome-cordova-plugins/sign-in-with-apple to re auth the user right before getting the refresh token so the authCode should still be valid.
I figured out my main issue. I renamed the AuthKey_XXXX.p8 file do not do that. I have the cloud function working locally.

Signing into Cognito app through Cypress programmatically , giving userpoolId and clientId is loading the app with out user data

I am trying to automate Sign In flow for an app with Cognito Identity Provider in Cypress.
This is the code I am using
cypress.json :
"username": :<userName>,
"password":<password>,
"userPoolId":<userPoolId>,
"clientId":<clientId>
commands.js
const Auth = require ( "aws-amplify" ).Auth;
import "cypress-localstorage-commands";
const username = Cypress.env("username");
const password = Cypress. env("password");
const userPoolId = Cypress. env("userPoolId");
const clientId = Cypress. env ("clientId");
const awsconfig = {
aws_user_pools_web_client_id: clientId,
aws_user_pools_id: userPoolId
};
Auth. configure (awsconfig) ;
Cypress.Commands.add("signIn", () => {
cy.then(() => Auth.signIn(username, password)).then((cognitoUser) => {
console.log("SIGNIN AUTH",cognitoUser)
const idToken = cognitoUser.signInUserSession.idToken.jwtToken;
console.log("IDTOKEN", idToken)
const accessToken = cognitoUser.signInUserSession.accessToken.jwtToken;
const makeKey = (name) => `CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.${cognitoUser.username}.${name}`;
cy.setLocalStorage(makeKey("accessToken"), accessToken);
cy.setLocalStorage(makeKey("idToken"), idToken);
cy.setLocalStorage(
`CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.LastAuthUser`,
cognitoUser.username
);
cy.setLocalStorage("amplify-signin-with-hostedUI","true")
cy.visit("<loginUrl>");
});
cy.saveLocalStorage();
})
TestFile.js :
cy.signIn()
App is loading but user data is not loaded. It launches the app with message - "Contact your administrator to start using the app", which is the message shown when the user is not added . But the user is added to the app. Could some one help me understand If I am missing something.User access token , idToken , refreshToken is stored correctly in the local Storage.

Does signing of the JWT require internet access?

JWT process is to sign JWT and use JWT to retrieve an access token. This access token is used to call Google APIs.
We use Google auth library to sign JWT. IS this signing process container locally or does it require internet access to another service to sign the JWT?
In order to see the flow for retrieving the access token using the service account, for example, when it sees the script for retrieving the access token using Javascript, the value of jwt can be created without the web access. But, when the access token is retrieved using the jwt, it is required to access the endpoint of Google like https://www.googleapis.com/oauth2/v4/token. Ref
The script for retrieving the access token using the service account is as follows.
const private_key = "###"; // private_key of JSON file retrieved by creating Service Account
const client_email = "###"; // client_email of JSON file retrieved by creating Service Account
const scopes = ["https://www.googleapis.com/auth/drive.readonly"]; // Scopes
const url = "https://www.googleapis.com/oauth2/v4/token";
const header = { alg: "RS256", typ: "JWT" };
const now = Math.floor(Date.now() / 1000);
const claim = {
iss: client_email,
scope: scopes.join(" "),
aud: url,
exp: (now + 3600).toString(),
iat: now.toString(),
};
const signature = btoa(JSON.stringify(header)) + "." + btoa(JSON.stringify(claim));
const sign = new JSEncrypt();
sign.setPrivateKey(private_key);
const jwt = signature + "." + sign.sign(signature, CryptoJS.SHA256, "sha256");
const params = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
assertion: jwt,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
}),
};
const obj = await fetch(url, params).then((res) => res.json()).catch((err) => console.log(err));
console.log(obj);
Referencs:
Using OAuth 2.0 for Server to Server Applications
GetAccessTokenFromServiceAccount_js

How to Refresh Token Using Google OAuth2 (Javascript/REST)

I'm trying to get a new token from Google OAuth2 but I keep getting this error:
Here is my code (I'm using Expo to build React Native apps):
const uri = 'https://oauth2.googleapis.com/token'
const headerr = {
'Content-Type': 'Content-Type: application/x-www-form-urlencoded'
}
const bodyy = {
"client_id": '******************',
"refresh_token": `${refreshToken}`,
"grant_type":"refresh_token",
"access_type":"offline"
}
const fitnesss = await fetch(uri, {
method: "POST",
headers: headerr,
body: JSON.stringify(bodyy)
});
fitnesss.json().then(res => {
console.log(res)
})
Does anyone know how to solve this?
unsupported grant type means that the grant type refresh token is not supported with the language you are using.
The reason for this is that JavaScript is client sided which would mean that you would need to have the refresh token in the code. Anyone who viewed the source in the browser would be able to see and use your refresh token.
To use refresh tokens use a server sided language. For example Node.js
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
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 Google Drive API.
authorize(JSON.parse(content), listFiles);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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 getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getAccessToken(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);
});
});
}

Why is OAuth2 with Gmail Nodejs Nodemailer producing "Username and Password not accepted" error

OAuth2 is producing "Username and Password not accepted" error when try to send email with Gmail+ Nodejs+Nodemailer
Code - Nodejs - Nodemailer and xoauth2
var nodemailer = require("nodemailer");
var generator = require('xoauth2').createXOAuth2Generator({
user: "", // Your gmail address.
clientId: "",
clientSecret: "",
refreshToken: "",
});
// listen for token updates
// you probably want to store these to a db
generator.on('token', function(token){
console.log('New token for %s: %s', token.user, token.accessToken);
});
// login
var smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
xoauth2: generator
}
});
var mailOptions = {
to: "",
subject: 'Hello ', // Subject line
text: 'Hello world ', // plaintext body
html: '<b>Hello world </b>' // html body
};
smtpTransport.sendMail(mailOptions, function(error, info) {
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
}
smtpTransport.close();
});
issues:
I used Google OAuth2 playground to create the tokens, https://developers.google.com/oauthplayground/
It looks to grab a valid accessToken ok, using the refreshToken, (i.e. it prints the new access token on the screen.) No errors until it tries to send the email.
I added the optional accessToken: but got the same error. ( "Username and Password not accepted")
I am not 100% sure about the "username", the docs say it needs a "user" email address - I guess the email of the account that created to token, but is not 100% clear. I have tried several things and none worked.
I have searched the options on the gmail accounts, did not find anything that looks wrong.
Also, when I did this with Java, it needed the google userID rather than the email address, not sure why this is using the email address and the Java is using the UserId.
nodemailer fails with a "compose" scope
The problem was the "scope"
it fails with:
https://www.googleapis.com/auth/gmail.compose
but works ok if I use
https://mail.google.com/
Simply just do the following:
1- Get credentials.json file from here https://developers.google.com/gmail/api/quickstart/nodejs press enable the Gmail API and then choose Desktop app
2- Save this file somewhere along with your credentials file
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if(err){
return console.log('Error loading client secret file:', err);
}
// Authorize the client with credentials, then call the Gmail API.
authorize(JSON.parse(content), getAuth);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
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);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
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 getAuth(auth){
}
3 - Run this file by typing in your terminal: node THIS_FILE.js
4- You'll have token.json file
5- take user information from credentials.json and token.json and fill them in the following function
const nodemailer = require('nodemailer');
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;
const email = 'gmail email'
const clientId = ''
const clientSecret = ''
const refresh = ''
const oauth2Client = new OAuth2(
clientId,
clientSecret,
);
oauth2Client.setCredentials({
refresh_token: refresh
});
const newAccessToken = oauth2Client.getAccessToken()
let transporter = nodemailer.createTransport(
{
service: 'Gmail',
auth: {
type: 'OAuth2',
user: email,
clientId: clientId,
clientSecret: clientSecret,
refreshToken: refresh,
accessToken: newAccessToken
}
},
{
// default message fields
// sender info
from: 'Firstname Lastname <your gmail email>'
}
);
const mailOptions = {
from: email,
to: "",
subject: "Node.js Email with Secure OAuth",
generateTextFromHTML: true,
html: "<b>test</b>"
};
transporter.sendMail(mailOptions, (error, response) => {
error ? console.log(error) : console.log(response);
transporter.close();
});
If your problem is the scopes, here is some help to fix
Tried to add this as an edit to the top answer but it was rejected, don't really know why this is off topic?
See the note here: https://nodemailer.com/smtp/oauth2/#troubleshooting
How to modify the scopes
The scopes are baked into the authorization step when you get your first refresh_token. If you are generating your refresh token via code (for example using the Node.js sample) then the revised scope needs to be set when you request your authUrl.
For the Node.js sample you need to modify SCOPES:
// If modifying these scopes, delete token.json.
-const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
+const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
And then the call to oAuth2Client.generateAuthUrl will produce a url that will request authorization from the user to accept full access.
from the Node.js sample:
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});

Resources