I am creating one app using react native expo, which allow end user to login by their google account , and then applicaton try to save the access_token so that server based applicatin can use this to send the email on their behalf ,
But when using google sing in , i am not getting refresh token and not able to send the email ,
Here is code example which i am using
I tried below method to get the access request
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId: "XXXXXXX",
androidClientId:"XXXXXXX",
iosClientId:"XXXXXXX"
});
const [initializing, setInitializing] = useState(true);
const [user, setUser] = useState();
const sendNotification=useNotification()
//console.log(sendNotification)
useEffect(() => {
if (response?.type === "success") {
const { id_token } = response.params;
const auth = getAuth();
const credential = GoogleAuthProvider.credential(id_token);
signInWithCredential(auth, credential);
let decoded = jwt_decode(id_token);
socialLogin(decoded)
}
}, [response]);
And on server using this code to sending email
const { google } = require('googleapis');
const path = require('path');
const fs = require('fs');
const credentials = require('./credentials.json');
// Replace with the code you received from Google
const code = 'XXXXXXX';
//const code="XXXXXXX"
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 }) => {
console.log('first')
const tokenPath = path.join(__dirname, 'token.json');
fs.writeFileSync(tokenPath, JSON.stringify(tokens));
console.log('Access token and refresh token stored to token.json');
}).catch(err=>console.log(err));
async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: YOUR_CLIENT_ID_HERE,
scopes: ["profile", "email"],
});
if (result.type === "success") {
onSignIn(result);
return result.accessToken;
} else {
return { cancelled: true };
}
} catch (e) {
return { error: true };
}
}
Well, I tried to create an application with Google login. To use the Google Sign-In method in a React Native Expo app, you will need to perform the following steps:
Set up a project in the Google Cloud Console and obtain a configuration file for your app.
Install the expo-google-sign-in package in your React Native app.
Import the GoogleSignIn object from the expo-google-sign-in package and use the initAsync method to initialize the Google Sign-In process.
Use the GoogleSignIn.askForPlayServicesAsync method to check if the device has the required Google Play Services installed.
Use the GoogleSignIn.signInAsync method to prompt the user to sign in with their Google account.
Once the user has signed in, you can use the accessToken and refreshToken properties of the returned object to make authorized requests to the Google APIs.
The code lines for the above steps are:
import { GoogleSignIn } from 'expo-google-sign-in';
// Initialize the Google Sign-In process
await GoogleSignIn.initAsync({
// Your config. values
});
// Check if the device has the required Google Play Services installed
const isPlayServicesAvailable = await GoogleSignIn.askForPlayServicesAsync();
if (!isPlayServicesAvailable) {
console.error('Google Play services are not available on this device.');
return;
}
// Prompt the user to sign in with their Google account
const { accessToken, refreshToken } = await GoogleSignIn.signInAsync();
I'm trying to get my expo AuthSession to work (sign up with google) in my react native app. I have been following this clip (https://www.youtube.com/watch?v=YX7IWOQIKA0) on youtube but get an error as soon as i try to navigate to the login screen.
The error message i get is:
Cannot use the AuthSession proxy because the project full name is not defined. Prefer AuthRequest (with the useProxy option set to false) in combination with an Expo Development Client build of your application. To continue using the AuthSession proxy, specify the project full name (#owner/slug) using the projectNameForProxy option.
Notes:
The app is working overall but breaks completely when i try to navigate to the LoginScreen where the code below exists. I have checked that the "https://auth.expo.io/#owner/slug" in the Google Cloud Credentials is correct. Have i missed to add something that i should've? I should also add that i am aiming to get this to work on both IOS and Android devices.
My relevant code to the problem is in my LoginScreen.tsx and it looks like this:
Imports:
import * as WebBrowser from 'expo-web-browser';
import * as Google from 'expo-auth-session/providers/google';
WebBrowser.maybeCompleteAuthSession();
GoogleAuthRequest:
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: '*THE CLIENT ID FROM GOOGLE CLOUD CREDENTIALS IS PASTED HERE*',
iosClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
androidClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
webClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
});
Code that runs when pressing "continue with google":
const signUpGoogleHandler = async () => {
const response = await promptAsync();
if (response.type === 'success') {
const {access_token } = response.params;
console.log('res params ', access_token);
}
console.log('test123');
const provider = new GoogleAuthProvider();
await signInWithRedirect(auth, provider);
getRedirectResult(auth)
.then((result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
const user = result.user;
}).catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
const email = error.customData.email;
const credential = GoogleAuthProvider.credentialFromError(error);
})
}
How my Client ID for my Web application is setup in Google Cloud (the Authorized redirect URIs is written as https://auth.expo.io/#myusername/slugname):
Client ID for Web application
I am building a Next JS app that has Github Login through Auth0 and uses the Octokit to fetch user info / repos.
In order to get the IDP I had to setup a management api in auth0. https://community.auth0.com/t/can-i-get-the-github-access-token/47237 which I have setup in my NodeJs server to hide the management api token as : GET /getaccesstoken endpoint
On the client side : /chooserepo page, I have the following code :
const chooserepo = (props) => {
const octokit = new Octokit({
auth: props.accessToken,
});
async function run() {
const res = await octokit.request("GET /user");
console.log("authenticated as ", res.data);
}
run();
And
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps({ req, params }) {
let { user } = getSession(req);
console.log("user from get session ", user);
let url = "http://localhost:4000/getaccesstoken/" + user.sub;
let data = await fetch(url);
let resData = await data.text();
return {
props: { accessToken: resData }, // will be passed to the page component as props
};
},
});
However, I keep getting Bad credentials error. If I directly put the access token in the Octokit it seems to work well, but doesn't work when it's fetching the access token from the server.
It seems like Octokit instance is created before server side props are sent. How do I fix it ?
I figured out the error by comparing the difference between the request headers when hardcoding and fetching access token from server. Turns out quotes and backslashes need to be replaced (and aren't visible when just console logging)
To give some context, I am a front end dev tasked with intergrating salesforce into a react app. This is a new learning curve for me as I am a SF newbie. I have been looking for a way to make intergration easier and I came across a video that showed how I can use a node packaged called JSforce to auth and fetch data from SF to my node express backend. I did as the video suggested but something appears to be not working as I am not console logging anything. Can anyone who has experience in using Jsforce take a look at my code below and let me know where I have gone wrong?
const express = require('express');
const app = express();
const port = 5000;
const jsforce = require('jsforce');
const username = 'blah';
const password = 'blah+ security token';
var conn = new jsforce.Connection({
// you can change loginUrl to connect to sandbox or prerelease env.
loginUrl: 'https://tahina-test2-dev-ed.my.salesforce.com/'
});
conn.login(username, password, function(err, userInfo) {
if (err) {
return console.error(err);
}
// Now you can get the access token and instance URL information.
// Save them to establish connection next time.
console.log(conn.accessToken);
console.log(conn.instanceUrl);
// logged in user property
console.log('User ID: ' + userInfo.id);
console.log('Org ID: ' + userInfo.organizationId);
// ...
conn.sobject('Account').retrieve('0012X000022HhE5QAK', function(err, account) {
if (err) {
return console.error(err);
}
console.log('Name : ' + account.Name);
// ...
});
});
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`));
I have an Express App ( hosted on Heroku ) which i'm using to handle intents from Dialogflow and make callouts to APEX REST Webservice classes (to get data from Salesforce) and then show the results back on Google Assistant.
For authentication, i'm trying to implement OAuth, and hence I've created Connected App on Salesforce.
On Google Actions under Account Linking i've mentioned the 'Authorization URL' as Express App URL (something like https://testBot.herokuapp.com/authorization) and 'Client Id issued by your Actions to Google' as Consumer Key from Salesforce Connected App and lastly 'Client Secret' as Salesforce Connected App Consumer Secret. Also, my Token URL is like https://testBot.herokuapp.com/token.
On Express i've created routes, first to handle the request coming in for authorization (to get authorization code) and then secondly on the callback route (this is the callback URL on Salesforce Connected App) as mentioned on Implement OAuth account linking i've redirected to redirect_uri (of the form https://oauth-redirect.googleusercontent.com/r/MY_PROJECT_ID) with authorization code and state as parameters. This is how the uri looks https://oauth-redirect.googleusercontent.com/r/MY_PROJECT_ID?code=AUTHORIZATION_CODE&state=STATE_STRING. Now on the 3rd route (https://testBot.herokuapp.com/token), logic is written to exchange authorization code for an access token and a refresh token. Note that the token exchange endpoint responds to POST requests.
Now as per official documentation , Google stores the access token and the refresh token for the user. So, what this means is that Conversation or conv object should hold the access token values however when I try to access the same and then make a callout to the APEX Webservice I could see that conv.user.accessToken gives undefined and hence the callout is also unsuccessful (error : INVALID_SESSION_ID: Session expired or invalid) even after successful authentication.
My question is why i'm not getting the access token from CONV and if this is expected (am I reading the documentation incorrectly) how am I supposed to get the access token ?
Here is the express code:
const express = require('express');
const bodyParser = require('body-parser');
const jsforce = require('jsforce');
const { dialogflow } = require('actions-on-google');
const {
SimpleResponse,
BasicCard,
SignIn,
Image,
Suggestions,
Button
} = require('actions-on-google');
var options;
var timeOut = 3600;
var port = process.env.PORT || 3000;
var conn = {};
const expApp = express().use(bodyParser.json());
expApp.use(bodyParser.urlencoded());
//app instance
const app = dialogflow({
debug: true
});
const oauth2 = new jsforce.OAuth2({
clientId: process.env.SALESFORCE_CONSUMER_KEY,
clientSecret: process.env.SALESFORCE_CONSUMER_SECRET,
redirectUri: 'https://testbot.herokuapp.com/callback'
});
expApp.get('/authorize', function(req, res) {
var queryParams = req.query;
console.log('this is the first request: '+req);
res.redirect(oauth2.getAuthorizationUrl({ state: queryParams.state }));
});
expApp.get('/callback', function(req,res) {
var queryParams = req.query;
console.log('Request came for access callback');
console.log('Query params in callback uri is ', req.query);
let redirectUri = `${process.env.GOOGLE_REDIRECT_URI}?code=${queryParams.code}&state=${queryParams.state}`;
console.log('Google redirecturi is ', redirectUri);
res.redirect(redirectUri);
});
expApp.post('/token', function(req, res) {
console.log('Request came for accesstoken');
console.log('query params are-->', req.body);
console.log('req query-->', req.query);
res.setHeader('Content-Type', 'application/json');
if (req.body.client_id != process.env.SALESFORCE_CONSUMER_KEY) {
console.log('Invalid Client ID');
return res.status(400).send('Invalid Client ID');
}
if (req.body.client_secret != process.env.SALESFORCE_CONSUMER_SECRET) {
console.log('Invalid Client Ksecret');
return res.status(400).send('Invalid Client ID');
}
if (req.body.grant_type) {
if (req.body.grant_type == 'authorization_code') {
console.log('Fetching token from salesforce');
oauth2.requestToken(req.body.code, (err, tokenResponse) => {
if (err) {
console.log(err.message);
return res.status(400).json({ "error": "invalid_grant" });
}
console.log('Token respons: ',tokenResponse);
var googleToken = {
token_type: tokenResponse.token_type,
access_token: tokenResponse.access_token,
refresh_token: tokenResponse.refresh_token,
expires_in: timeOut
};
console.log('Token response for auth code', googleToken);
res.status(200).json(googleToken);
});
}
else if (req.body.grant_type == 'refresh_token') {
console.log('Fetching refresh token from salesforce');
oauth2.refreshToken(req.body.refresh_token, (err, tokenResponse) => {
if (err) {
console.log(err.message);
return res.status(400).json({ "error": "invalid_grant" });
}
console.log('Token response in refresh token: ',tokenResponse);
var googleToken = { token_type: tokenResponse.token_type, access_token: tokenResponse.access_token, expires_in: timeOut };
console.log('Token response for auth code', googleToken);
res.status(200).json(googleToken);
});
}
} else {
res.send('Invalid parameter');
}
});
var createTask = function(oppName,taskSubject,taskPriority,conFName,conn){
return new Promise((resolve,reject)=>{
conn.apex.get("/createTask?oppName="+oppName+"&taskSubject="+taskSubject+"&taskPriority="+taskPriority+"&contactFirstName="+conFName,function(err, res){
if (err) {
console.log('error is --> ',err);
reject(err);
}
else{
console.log('res is --> ',res);
resolve(res);
}
});
});
};
app.intent('Default Welcome Intent', (conv) => {
console.log('Request came for account link flow start');
if(!conv.user.accessToken){
conv.ask(new SignIn());
}
else{
conv.ask('You are already signed in ');
}
});
app.intent('Get SignIn Info', (conv, params, signin) => {
console.log('Sign in info Intent');
console.log('Sign in content-->',signin);
if (signin.status === 'OK') {
conv.ask('Hola, thanks for signing in! What do you want to do next?') ;
}
else {
conv.ask('Something went wrong in the sign in process');
}
});
app.intent('Create Task on Opportunity', (conv, {oppName,taskSubject,taskPriority,contactFirstName} ) => {
console.log('conv: ',conv);
//this logs undefined
console.log('Access token from conv inside intent: ',conv.user.accessToken);
const opName = conv.parameters['oppName'];
const tskSbj = conv.parameters['taskSubject'];
const tskPr = conv.parameters['taskPriority'];
const conFName = conv.parameters['contactFirstName'];
console.log('Instance URL as stored in heroku process variable: ',process.env.INSTANCE_URL);
conn = new jsforce.Connection({
instanceUrl : process.env.INSTANCE_URL,
accessToken : conv.user.accessToken
});
return createTask(opName,tskSbj,tskPr,conFName,conn).then((resp) => {
conv.ask(new SimpleResponse({
speech:resp,
text:resp,
}));
});
});
expApp.get('/', function (req, res) {
res.send('Hello World!');
});
expApp.listen(port, function () {
expApp.post('/fulfillment', app);
console.log('Example app listening on port !');
});
So, on logging conversation.user I understood that conv.user.access.token is correct and not conv.user.accessToken. Hence, now the connection instance would look like:
conn = new jsforce.Connection({
instanceUrl : process.env.INSTANCE_URL,
accessToken : conv.user.acces.token
});
Now, get request on apex web service does send expected response !