Google Action Builder Account Linking with custom authentication in Node js - node.js

I'm creating Google Actions Builderaction console and I'm having some doubt in Account Linking. Should be like, need to authenticate account linking based on my database data.
Example:
While Account Linking if I wanted to pass a email (abc#gmail.com), that email should be active and only on that case Account Linking should be allow. So for this I want to write custom Node Js function.
So that I have used Link Type as oAuth and Authorization, Token URL I set with as my custom Node Js functions.
My doubt:
how to pass email id while link Account Linking.
After validate email how can I link account in Google Actions Builder.
My Node Js Code
Here I want to write function inside auth call back function inside if(result).
const express = require('express');
const port = 5003;
const app = express();
app.get('/', (req, res) =>{
res.send(`Welcome to Test App Nodejs`);
})
app.get('/auth', (req, res) =>{
var email = req.query.email;
userModel.findAll({
where: {
emailId: email,
status:1
}
}).then((result) =>{
if(result){
// Allow to account link
} else{
// to return Thanks for trying to account linking
}
}).catch((err)=>{
res.send(err);
})
});
app.listen(port, (req, res)=>{
console.log(`Test App runing with ${port}`)
})

There are a number of things about your question that don't fit with how Account Linking is meant to work, so it might make sense to get a brief overview of how Account Linking works.
The purpose of Account Linking is to provide a way that a user record that you maintain for your service gets associated with an Assistant account. This is done (broadly speaking) by the user authorizing Google to access basic information about the user's records in your system. This is done using OAuth2.
There are variants (authorizing using a mobile app, or authorizing the Google account to give you the user record), but they generally work the same way:
You authorize Google to get access to information
Google provides this information as part of the request sent to your webhook
So it does not exactly make sense for you to provide to your webhook an email address and expect it to link somehow. That isn't how this works. If anything - it makes it so you don't need to ask the user for their email address, you can just get it from the linked account.
If you are trying to build a webhook that does the authorization part, you'll need to have it handle OAuth2. This is a lot more than "pass an email address", however, and while it is not difficult, it can be tricky to get some security elements correct. This is usually best left to tools such as Auth0 or other identity providers.
You can also learn more about Account Linking and how it works in general with Action Builder.
Also, keep in mind that conversational actions will be shut down on June 13 2023.

Related

Get Google business reviews server side

I'm trying to get a list of reviews of my Google business through the API to display them on my website. But I can't figure out how to authenticate the API server side. The documentation only mentions OAuth2.0 authentication from the client side with redirect URLs, but there won't be a client going to a confirmation page in this case.
I just want to be able to perform this request in Node.js:
GET https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/reviews
I've already submitted the application and been approved for a Business API and enabled the various APIs in my account. I have created OAuth2.0 credentials. I'm just not sure how to move forward from here.
How can I authenticate Google Business API requests on the server?
I ended up putting together an answer through lots of searching. Google documentation is all over the place.
A basic example of getting reviews is below. But to get it to work there are a few steps first.
You'll need to add a Service Account in your API credentials page
The name, ID, and description aren't particularly important. Just something that makes sense to you
Go to the service account details -> keys -> add key -> create new key -> JSON
This will download a key file to your computer. Keep it private.
Grant domain wide delegation for your service account
To do this, you'll need to be an admin of the account if you're part of an organisation
It will ask for a Client ID, but it is called Unique ID in the service account details. They're the same thing.
Add whatever scopes you need for the services you want to access. For reviews, the scope listed in the example below is enough.
The subject field in google.auth.JWT needs to be an admin of the account. I used my own email.
That should be it! You should now be able to fill out the values in the example below and access the API from a server. Other services may require different scopes.
You can get account and location info from the API docs. The endpoints and data formats are fairly well documented. Just authentication isn't very well explained it seems.
import axios from 'axios';
import {google} from 'googleapis';
import key from './key.json' assert {type: 'json'};
main();
async function main(){
const reviews=await getReviews();
}
async function getReviews(){
const token=await authenticate();
const accountId='YOUR ACCOUNT ID';
const locationId='YOUR LOCATION ID';
const url=`https://mybusiness.googleapis.com/v4/accounts/`+
`${accountId}/locations/${locationId}/reviews`;
const resp=await axios.get(url, {
headers: {
authorization: `Bearer ${token}`
}
});
return resp.data.reviews;
}
async function authenticate(){
const scopes=[
'https://www.googleapis.com/auth/business.manage'
];
const jwt=new google.auth.JWT({
email: key.client_email,
key: key.private_key,
subject: 'ADMIN EMAIL',
scopes
});
const resp=await jwt.authorize();
return resp.access_token.replace(/\.{2,}/g, '');
}

Single user is being logged in across all devices

I was building a project on node.js recently, I came across this bug of user authentication. The problem is that after I log in from one account, if at the same time I refresh the home page of the website from another device, it redirects to the dashboard of the first user account. In other words, a single user is getting logged in on all the devices over the network. If suppose I don't refresh the page, and I log in normally on the application, it works fine. This problem is happening, both on the localhost as well as after hosting it on Heroku. Technologies used- node.js for handling back-end views and URLs. HTML, CSS, JavaScript for the front-end. Firebase for the database and authentication. Here is the code for the login part-
const express = require("express");
const path = require("path");
//Create Router Object.
const router = express.Router();
//Main Login Page (GET)
router.get("/", (request, response) => {
response.sendFile(path.resolve("./views/html/login.html"));
});
//Main Login Page (POST)
router.post("/", (request, response) => {
let email = request.body.email;
let password = request.body.password;
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
firebase.auth().signInWithEmailAndPassword(email, password)
.then(r => {
let user = firebase.auth().currentUser;
if (user.emailVerified)
response.redirect('/dashboard');
else
response.send("<h1>Please Verify your email address.</h1>");
})
.catch(error => {
console.log(error);
response.send("<h1>Invalid Credentials</h1>");
});
});
Please can someone help me by resolve this bug in my project?
Calling firebase.auth().signInWithEmailAndPassword(email, password) signs the user in on the location wherever you call this code. Since you run this in an express.js app in Node.js, the user is signed in to that Node.js process, until you sign them out again. This is why the Firebase Authentication SDK you are using is only meant to be used in client-side applications, where this behavior is working as intended.
When integrating Firebase Authentication in a server-side process, you should use the Firebase Admin SDK. This Admin SDK has no concept of a currently logged in user, and no signInWithEmailAndPassword. Instead, if you need to know the identity of the user accessing the server-side code, you'll:
Sign the user in in their client-side app with the Firebase Authentication SDK.
Get the ID token for that user on the client, and pass that with your call to the server.
On the server, verify that ID token, to ensure the user is who they claim they are.
Then use the information on the user's identity to determine if they're authorized to access the information.
For more on this, see the Firebase documentation on verifying ID tokens.

Actions on google account linking returns error

My Dialogflow agent is integrated with Google Assistant, I am trying to extend it with Actions on Google. Now we are trying Account linking with our client's Oauth server. Auth URL, Redirect URI, Client secret, and Client Id are provided by them and the same is configured in the account linking section of my project in Actions Console with authorization code flow. Using the same details, I was able to implement the account linking to my Alexa Skill. I am using Node.js as my webhook, the same is using actions-on-google package for handling intents. I got the below piece of code from the documents, the same is added to my webhook
const app = dialogflow({ debug: false })
app.intent('Get Signin', (conv, params, signin) => {
console.log("signin", signin)
if (signin.status === 'OK') {
const access = conv.user.access.token // possibly do something with access token
conv.ask('Great, thanks for signing in! What do you want to do next?')
} else {
conv.ask(`I won't be able to save your data, but what do you want to do next?`)
}
})
app.intent('Default Welcome Intent', (conv) => {
conv.ask(new SignIn());
})
Whenever I invoke the agent, it is asking me to Link the account with Google(I'm not trying to login with Google though) the browser opens with My client's OAuth authorization endpoint, I have provided the login information and it returns back to the Google Assistant app with an error message saying
Sorry, something went wrong, so I couldn't sign you in. But you can
try again later
I tried outputting the variable sign in to the console and I can see the below error
{
'#type': 'type.googleapis.com/google.actions.v2.SignInValue',
status: 'ERROR'
}
In the Google Assiatant App, I could see the redirect URI params as
https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID?code=AUTHORIZATION_CODE&state=STATE_STRING
I need clarity in below points
Can I implement Account Linking with other Oauth Servers in Actions on Google other than Sign in with Google?
Are there any other steps I am missing with the code or Dialogflow and Actions on Google
Console?
Help here is really appreciated. Please let me know if the provided information is not sufficient.
The issue was solved, I have to check the checkbox in Account linking settings which says "Google to transmit ClientId ans secret via HTTP basic auth header"

GCP Consume a REST API after OAuth in Node.js

I am working to implement a Node.js webapp to be deployed on GCP App Engine.
Following the Node.js Bookshelf App sample, I did manage to implement a basic user authentication flow using the passport-google-oauth20 and retrieve basic profile information. I basically just got rid of what was not needed for my purposes
My custom code is available at: gist.github.com/vdenotaris/3a6dcd713e4c3ee3a973aa00cf0a45b0.
However, I would now like to consume a GCP Cloud Storage API to retrieve all the storage objects within a given buckets with the logged identity.
This should be possible by:
adding a proper scope for the request.
authenticating the REST requests using the user session token obtained via OAuth.
About the post-auth handler, the documentation says:
After you obtain credentials, you can store information about the
user. Passport.js automatically serializes the user to the session.
After the user’s information is in the session, you can make a couple
of middleware functions to make it easier to work with authentication.
// Middleware that requires the user to be logged in. If the user is not logged
// in, it will redirect the user to authorize the application and then return
// them to the original URL they requested.
function authRequired (req, res, next) {
if (!req.user) {
req.session.oauth2return = req.originalUrl;
return res.redirect('/auth/login');
}
next();
}
// Middleware that exposes the user's profile as well as login/logout URLs to
// any templates. These are available as `profile`, `login`, and `logout`.
function addTemplateVariables (req, res, next) {
res.locals.profile = req.user;
res.locals.login = `/auth/login?return=${encodeURIComponent(req.originalUrl)}`;
res.locals.logout = `/auth/logout?return=${encodeURIComponent(req.originalUrl)}`;
next();
}
But I do not see where the token is stored, how can I retrieve it and how to use it to consume a web-service (in my case, GCP storage).
I am not at all a node.js expert, so it would be nice having a bit more clarity on that: could someone explain me how to proceed in consuming a REST API using the logged user credentials (thus IAM/ACL privileges)?
If you want to access Cloud Storage through the use of a token obtained with OAuth, when the application requires user data, it will prompt a consent screen, asking for the user to authorize the app to get some of their data. If the user approves, an access token is generated, which can be attached to the user's request. This is better explained here.
If you plan to run your application in Google App Engine, there will be a service account prepared with the necessary authentication information, so no further setup is required. You may need to generate the service account credentials (generally in JSON format), that have to be added to the GOOGLE_APPLICATION_CREDENTIALS environment variable in gcloud.
Here is an example of how to authenticate and consume a REST API with the token that was obtained in the previous step. This, for example, would be a request to list objects stored in a bucket:
GET /storage/v1/b/example-bucket/o HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer [YOUR_TOKEN]

iOS & node.js: how to verify passed access token?

I have an iOS which uses OAuth and OAuth2 providers (Facebook, google, twitter, etc) to validate a user and provide access tokens. Apart from minimal data such as name and email address, the app doesn't uses these services for anything except authentication.
The app then sends the access token to a server to indicate that the user is authenticated.
The server is written in Node.js and before doing anything it needs to validate the supplied access token against the correct OAuth* service.
I've been looking around, but so far all the node.js authentication modules I've found appear to be for logging in and authenticating through web pages supplied by the server.
Does anyone know any node.js modules that can do simple validation of a supplied access token?
To the best of my knowledge (and as far as I can tell from reading the specifications) the OAuth and OAuth 2 specs do not specify a single endpoint for access token validation. That means you will need custom code for each of the providers to validate an access token only.
I looked up what to do for the endpoints you specified:
Facebook
It seems others have used the graph API's 'me' endpoint for Facebook to check if the token is valid. Basically, request:
https://graph.facebook.com/me?access_token={accessToken}
Google
Google have a dedicated debugging endpoint for getting access token information, with nice documentation, too. Basically, request:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={accessToken}
However, they recommend that you don't do this for production:
The tokeninfo endpoint is useful for debugging but for production
purposes, retrieve Google's public keys from the keys endpoint and
perform the validation locally. You should retrieve the keys URI from
the Discovery document using the jwks_uri metadata value. Requests to
the debugging endpoint may be throttled or otherwise subject to
intermittent errors.
Since Google changes its public keys only infrequently, you can cache
them using the cache directives of the HTTP response and, in the vast
majority of cases, perform local validation much more efficiently than
by using the tokeninfo endpoint. This validation requires retrieving
and parsing certificates, and making the appropriate cryptographic
calls to check the signature. Fortunately, there are well-debugged
libraries available in a wide variety of languages to accomplish this
(see jwt.io).
Twitter
Twitter doesn't seem to have a really obvious way to do this. I would suspect that because the account settings data is pretty static, that might be the best way of verifying (fetching tweets would presumably have a higher latency?), so you can request (with the appropriate OAuth signature etc.):
https://api.twitter.com/1.1/account/settings.json
Note that this API is rate-limited to 15 times per window.
All in all this seems trickier than it would first appear. It might be a better idea to implement some kind of session/auth support on the server. Basically, you could verify the external OAuth token you get once, and then assign the user some session token of your own with which you authenticate with the user ID (email, FB id, whatever) on your own server, rather than continuing to make requests to the OAuth providers for every request you get yourself.
For google in production, install google-auth-library (npm install google-auth-library --save) and use the following:
const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client(GOOGLE_CLIENT_ID); // Replace by your client ID
async function verifyGoogleToken(token) {
const ticket = await client.verifyIdToken({
idToken: token,
audience: GOOGLE_CLIENT_ID // Replace by your client ID
});
const payload = ticket.getPayload();
return payload;
}
router.post("/auth/google", (req, res, next) => {
verifyGoogleToken(req.body.idToken).then(user => {
console.log(user); // Token is valid, do whatever you want with the user
})
.catch(console.error); // Token invalid
});
More info on Authenticate google token with a backend server, examples for node.js, java, python and php can be found.
For Facebook, do an https request like:
const https = require('https');
router.post("/auth/facebook", (req, res, next) => {
const options = {
hostname: 'graph.facebook.com',
port: 443,
path: '/me?access_token=' + req.body.authToken,
method: 'GET'
}
const request = https.get(options, response => {
response.on('data', function (user) {
user = JSON.parse(user.toString());
console.log(user);
});
})
request.on('error', (message) => {
console.error(message);
});
request.end();
})
In production for google you can use:
https://www.npmjs.com/package/google-auth-library
const ticket = client.verifyIdToken({
idToken: ctx.request.body.idToken,
audience: process.env.GOOGLE_CLIENTID
})
To get info about token from Google use, be careful vith version api
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={accessToken}

Resources