I want to implement Google 2FA Authentication in node js How can i do this?
I used speakeasy js library like below :
const qrcode = require('qrcode');
const secret = speakeasy.generateSecret({
name: 'test'//constants.SPEAK_SECRET_CODE
});
var qr_data = '';
await qrcode.toDataURL(secret.otpauth_url).then(qr => {
qr_data = qr;
});
return { qr_data };
After this i created html file with this code <img src="qr_data"> and with google authenticator app i scanned barcode it worked succesfully.
Then i tried to this in order to verify google otp :
var verified = speakeasy.totp.verify({
secret: secret.ascii,
encoding: 'ascci',
token: otp,
});
console.log(verified);
But verified variable is always false,although I entered the correct code
Related
i have created a new project and i want to send email on signup and password reset etc , when i used old api key it gave error **Error:
The provided authorization grant is invalid, expired, or revoked**
but when i ran the older projects with same api-key it worked fine , also i tried new api-key but it is giving same error
const nodemailer = require("nodemailer")
const sTransport = require("nodemailer-sendgrid-transport")
const options = {
auth:{
api_key:process.env.SEND_GRID_KEY
}
}
const mailer = nodemailer.createTransport(sTransport(options))
router.get("/", async (req, res) => {
let products = await productSchema.find({});
let sendmail =await mailer.sendMail({
to:req.user.email,
from:process.env.FROM,
subject:"chekcing the key",
html:"just checking the send grid api !!"
})
I am logging users in via their domain Google accounts using passport.js. This works great, but now I need to give this application access to a few Google API's (drive, sheets, etc).
When a user logs in, a message appears in the logs, that makes it seem like passport has all the required info:
info: [06/Jun/2019:21:24:37 +0000] "302 GET /auth/callback?code=** USER ACCESS TOKEN HERE **&scope=email%20profile%20https://www.googleapis.com/auth/drive.file%20https://www.googleapis.com/auth/spreadsheets%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/drive HTTP/1.1" [46]
This is achieved by passing the appended scopes via passport.authenticate(), which presents the user with the "Grant access to these things on your Google account to this app?" screen :
//Initial auth call to Google
router.get('/',
passport.authenticate('google', {
hd: 'edmonds.wednet.edu',
scope: [
'email',
'profile',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/spreadsheets'
],
prompt: 'select_account'
})
);
However, when I go and try to call an API with something like:
const {google} = require('googleapis');
const sheets = google.sheets({version: 'v4', auth});
router.post('/gsCreate', function(req,res,next){
sheets.spreadsheets.create({
// Details here.....
});
});
I get nothing but errors (the current one is debug: authClient.request is not a function)
My question is: Is it possible for me to use a setup like this, asking the user to log in and grant permissions once, and then somehow save that to their user session via passport?
I had the same question, but I was able to access Google Gmail API functionalities along with Passport.js user authentication by specifying 'scopes' using the following process.
First, create a file to setup the passport-google-strategy in nodejs as follows.
passport_setup.js
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20')
const fs = require("fs");
const path = require('path');
//make OAuth2 Credentials file using Google Developer console and download it(credentials.json)
//replace the 'web' using 'installed' in the file downloaded
var pathToJson = path.resolve(__dirname, './credentials.json');
const config = JSON.parse(fs.readFileSync(pathToJson));
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser((id, done) => {
const query = { _id: id }
Users.findOne(query, (err, user) => {
if (err) {
res.status(500).json(err);
} else {
done(null, user)
}
})
})
//create a google startergy including following details
passport.use(
new GoogleStrategy({
clientID: config.installed.client_id,
clientSecret: config.installed.client_secret,
callbackURL: config.installed.redirect_uris[0]
}, (accessToken, refreshToken,otherTokenDetails, user, done) => {
//in here you can access all token details to given API scope
//and i have created file from that details
let tokens = {
access_token: accessToken,
refresh_token: refreshToken,
scope: otherTokenDetails.scope,
token_type: otherTokenDetails.token_type,
expiry_date:otherTokenDetails.expires_in
}
let data = JSON.stringify(tokens);
fs.writeFileSync('./tokens.json', data);
//you will get a "user" object which will include the google id, name details,
//email etc, using that details you can do persist user data in your DB or can check
//whether the user already exists
//after persisting user data to a DB call done
//better to use your DB user objects in the done method
done(null, user)
})
)
Then create your index.js file in nodejs for API route management and to call send method of Gmail API.
Also, run the following command to install "google-apis"
npm install googleapis#39 --save
index.js
const express = require("express")
//import passport_setup.js
const passportSetup = require('./passport_setup')
const cookieSeesion = require('cookie-session');
const passport = require("passport");
//import google api
const { google } = require('googleapis');
//read credentials file you obtained from google developer console
const fs = require("fs");
const path = require('path');
var pathToJson_1 = path.resolve(__dirname, './credentials.json');
const credentials = JSON.parse(fs.readFileSync(pathToJson_1));
//get Express functionalities to app
const app = express();
// **Middleware Operations**//
//cookie encryption
app.use(cookieSeesion({
name:'Reserve It',
maxAge: 1*60*60*1000,
keys: ['ranmalc6h12o6dewage']
}))
//initialize passort session handling
app.use(passport.initialize())
app.use(passport.session())
app.use(express.json());
//**API urls**//
//route to authenticate users using google by calling google stratergy in passport_setup.js
//mention access levels of API you want in the scope
app.get("/google", passport.authenticate('google', {
scope: ['profile',
'email',
'https://mail.google.com/'
],
accessType: 'offline',
prompt: 'consent'
}))
//redirected route after obtaining 'code' from user authentication with API scopes
app.get("/google/redirect", passport.authenticate('google'), (req, res) => {
try {
//read token file you saved earlier in passport_setup.js
var pathToJson_2 = path.resolve(__dirname, './tokens.json');
//get tokens to details to object
const tokens = JSON.parse(fs.readFileSync(pathToJson_2));
//extract credential details
const { client_secret, client_id, redirect_uris } = credentials.installed
//make OAuth2 object
const oAuth2Client = new google.auth.OAuth2(client_id,
client_secret,
redirect_uris[0])
// set token details to OAuth2 object
oAuth2Client.setCredentials(tokens)
//create gmail object to call APIs
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
//call gmail APIs message send method
gmail.users.messages.send({
userId: 'me',//'me' indicate current logged in user id
resource: {
raw: //<email content>
}
}, (err, res) => {
if (err) {
console.log('The API returned an error: ' + err)
throw err
}
console.log('Email Status : ' + res.status)
console.log('Email Status Text : ' + res.statusText)
})
res.status(200).json({ status:true })
} catch (err) {
res.status(500).json(err)
}
})
app.listen(3000, () => { console.log('Server Satrted at port 3000') })
You can separate the routes in the index.js file to different files for clarity using express.Router()
If you want to call another Google API service just change this code segment and code below that;
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
gmail.users.messages.send(....Send Method internal implementation given above....)
For Google Drive:
const drive = google.drive({version: 'v3', auth: oAuth2Client});
drive.files.list(...Refer "Google Drive API" documentation for more details....)
I believe you can't use passport.js for three-legged oauth for APIs like Sheets or Drive.
Have a look at the Using OAuth for web servers documentation instead.
user835611 has the correct answer, as that page explains everything quite nicely. However, if you still need more, the below link really helped me to understand how this works.
https://github.com/googleapis/google-auth-library-nodejs#oauth2
My setup consists of an AWS API Gateway with IAM access control and AWS cognito for log in.
I access the API already from an Android app and would now like to build a web app (angular2) to do the same.
On Android, I'm using the AWSCognitoCredentialsProvider to supply the API SDK with the required credential. (http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk.html)
Unfortunately I cannot figure how / if I can do that with the javascript SDK?
I have no trouble using cognito to log in and get the session ID, access token etc. However, the API SDK requires me to provide accessKey and secretKey.
Here's the relevant code snippet from the generated API SDK:
var authType = 'NONE';
if (sigV4ClientConfig.accessKey !== undefined && sigV4ClientConfig.accessKey !== '' && sigV4ClientConfig.secretKey !== undefined && sigV4ClientConfig.secretKey !== '') {
authType = 'AWS_IAM';
}
In other words, I have this part working (from some example code):
static authenticate(username:string, password:string, callback:CognitoCallback) {
AWSCognito.config.update({accessKeyId: 'anything', secretAccessKey: 'anything'})
let authenticationData = {
Username: username,
Password: password,
};
let authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
let userData = {
Username: username,
Pool: CognitoUtil.getUserPool()
};
console.log("Authenticating the user");
let cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
console.log(AWS.config);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
callback.cognitoCallback(null, result);
},
onFailure: function (err) {
callback.cognitoCallback(err.message, null);
},
});
}
and now I'd like to use this:
this.apigClient = apigClientFactory.newClient({
accessKey: "anything",
secretAccessKey: "anything",
sessionToken: "nothing",
region: 'eu-central-1'
How do I get accessKey, secretAccessKey and sessionToken out of my AWSCognito? I was unable to find any API for that so far...
Thank you Bob, for pointing me in the right direction! I've now figured it out and thus for completeness sake, here's the full solution to my problem:
From the service that creates the apigClient:
return CognitoUtil.getCredentials()
.then(() =>
this.apigClient = apigClientFactory.newClient({
accessKey: AWS.config.credentials.accessKeyId,
secretKey: AWS.config.credentials.secretAccessKey,
sessionToken: AWS.config.credentials.sessionToken,
region: 'eu-central-1'}));
The getCredentials() method, which is key to get the required temporary credentials:
public static getCredentials():Promise{
return new Promise((resolve, reject) => {
CognitoUtil.getIdToken({
callback() {
},
callbackWithParam(idTokenJwt:any) {
let url = 'cognito-idp.' + CognitoUtil._REGION.toLowerCase() + '.amazonaws.com/' + CognitoUtil._USER_POOL_ID;
let logins = {};
logins[url] = idTokenJwt;
let params = {
IdentityPoolId: CognitoUtil._IDENTITY_POOL_ID, /* required */
Logins: logins
};
AWS.config.region = CognitoUtil._REGION;
AWS.config.credentials = new AWS.CognitoIdentityCredentials(params);
AWS.config.credentials.refresh(result => {
console.log(AWS.config.credentials);
resolve();
});
}
});
});
}
So the key insight here was, that
I authenticate to the user pool (shown in my question)
I use that with an identity provider to retrieve temporary credentials (getCredentials)
I use the temporary credentials out of AWS.config.credentials to setup the apigClient
I hope this is helpful to someone else as well. Certainly the code that I just posted probably could use some refactoring, so any comments on that are very welcome!
Cognito is actually made of 3 different services:
Cognito Your User Pools - What you've integrated here
Cognito Sync - For syncing user preference data for users
Cognito Federated Identity - For federating identities (FB, Google or User Pools) into your account and generating credentials.
What the API Gateway client is expecting is credentials that come from Cognito Federated Identity.
See the Cognito documentation for integrating your user pool with Cognito Federated Identity.
I've created a Cognito User Pool. I can list the users and add the users using the AWSCognitoIdentityProviderClient from the Java AWS SDK.
However, I have a custom login page and I wish to take the entered username and password and authenticate against my User Pool. I don't see anywhere in the Java AWS SDK where I can pass credentials and get an authentication result from.
Edit: I can't get past this error:
NotAuthorizedException: Missing credentials in config
Relevant code:
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:087a3210-64f8-4dae-9e3c...' // your identity pool id here
});
AWSCognito.config.region = 'us-east-1';
AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:087a3210-64f8-4dae-9e3c...' // your identity pool id here
});
var poolData = {
UserPoolId: 'us-east-1_39RP...',
ClientId: 'ttsj9j5...',
ClientSecret: 'bkvkj9r8kl2ujrlu41c7krsb6r7nub2kb260gj3mgi...'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var authenticationData = {
Username: 'test#foo.com',
Password: 'foobarfoo',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: 'test#foo.com',
Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
},
onFailure: function (err) {
alert(err);
},
});
The AWS Java SDK includes APIs to authenticate users in a User Pool. You can authenticate a user using either the InitiateAuth api or AdminInitiateAuth api of the AWSCognitoIdentityProviderClient class. The difference between these two API is explained in the documentation. In short, for InitiateAuth, you need to perform SRP calculations and then pass it to the API, while in AdminInitiateAuth you can directly pass the username and password. You can read about the security implications in both cases and decide which one to use.
Documentation :
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html
API reference:
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminInitiateAuth.html
My working sample(Groovy):
def login() {
AWSCognitoIdentityProviderClient client = new AWSCognitoIdentityProviderClient()
println("Provider client: " + client)
client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
HashMap authParams = new HashMap<>()
authParams.put("USERNAME", "User1")
authParams.put("PASSWORD", "a*123")
AdminInitiateAuthRequest adminInitiateAuthRequest = new AdminInitiateAuthRequest()
.withClientId(<YOUR_CLIENT_ID>)
.withUserPoolId(<YOUR_USER_POOL_ID>)
.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH )
.withAuthParameters(authParams)
AdminInitiateAuthResult result = client.adminInitiateAuth(adminInitiateAuthRequest);
if (result != null) {
System.out.println("AdminInitiateAuthResult:");
System.out.println(result.toString());
} else {
System.out.println("No result available");
return;
}
}
Authentication is only supported via JavaScript, iOS and Android at this time. The necessary apis to authenticate are not part of the server SDKs (java, python et. all) during the beta. Using the JavaScript SDK is the recommended way of authenticating from your login page.
check here https://github.com/aws/amazon-cognito-identity-js
there is a missing line of code
This page http://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html is not updated
// Need to provide placeholder keys unless unauthorised user access is enabled for user pool
AWSCognito.config.update({accessKeyId: 'anything', secretAccessKey: 'anything'})
After including this I stopped having this error.
I'm doing some web based app that shows user calendar from database entries. In this web app user can authenticate it's Google Calendar account, through JavaScript library. Then I want to share this access authorization with my desktop service. Desktop service is written in C#.
Does anybody knows how can I fill up CalendarService class in C# having following code?
Javascript:
function auth() {
var config = {
'client_id': "myID",
'scope': "https://www.googleapis.com/auth/calendar"
};
gapi.auth.authorize(config, function () {
console.log('login complete');
console.log(gapi.auth.getToken());
});
}
Then there is my C# code in Windows Service
var tokenResponse = new TokenResponse
{
AccessToken = tokenCopiedFromJavascriptObject,
Issued = propertyCopiedFromJavascriptObject,
TokenType = "Bearer",
Scope = CalendarService.Scope.Calendar,
RefreshToken = "3600"
};
var authorizationCodeFlowInitializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientId = myIdForDesktopApp,
ClientSecret = ClientSecretForDestopApp
},
Scopes = new[] { CalendarService.Scope.Calendar },
Clock = Google.Apis.Util.SystemClock.Default
};
var authorizationCodeFlow = new GoogleAuthorizationCodeFlow(authorizationCodeFlowInitializer);
_credentials = new UserCredential(authorizationCodeFlow, _calendarId, tokenResponse);
_service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = _credentials,
ApplicationName = "Fast and Easy Reservation System"
});
const string meeting = "Some fancy meeting";
var e = _service.Events.QuickAdd("primary", meeting).Execute();
}
Is there any hope to get this working? For example I have 2 apps (web and desktop) in my Google API Project. These are within one project so I think it should work as a hybrid app and they should share one authorization token.
Object returned by JS auth function have following properties:
access_token
client_id
expires_at
expires_in
issued_at
response_type
scope
token_type
I've made Controler in ASP.NET MVC application to handle Javascript code and send token to my database.
In order to avoid incompatible application types (web and native), my Windows Service uses client_secrets.json file from web type application. In order to precisely control flow of application and somehow cheat that native service is web application, I have loaded just client_id and client_secret from client_secrets.json (web).
var flow = new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientId = "some-numbersAndLettersxxxxxxxxxx.apps.googleusercontent.com",
ClientSecret = "xxxxxxxxxxxxxx"
}
});
Then, I loaded token from database in my app and assigned it.
var token = new TokenResponse
{
RefreshToken = "ya29.UAH2llQasSADfga32fWay3aqi1pb0AUwAWy_WzbPNyNkYCRPhe5_zBbEG94TZafV8pBMHzC8Q"
};
After well prepared flow and token I was able to create credentials object.
var credential = new UserCredential(flow, clientId, token);
Now, native app pretends to be web application and it can access all user data without additional consent screens.