How to refresh access token in electron app?google oauth2.0 - electron

I use this api to provide google login function for my electron app
https://github.com/googleapis/google-auth-library-nodejs
My access token expires after 3600 seconds
I don’t want my users to log in again after 3600 seconds
How can I make the token refresh automatically?
I try to use the document example code on the my app
But it doesn't seem to work
How can I get a new access_token
I try the code below to get a new access_token
But nothing happens
const { app, BrowserWindow, screen } = require('electron');
const fs = require('fs');
const { google } = require('googleapis'); // auth node js
googleOAuth2Login();
function googleOAuth2Login() {
const SCOPES = ['https://www.googleapis.com/auth/drive'];
const TOKEN_PATH = 'token.json';
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
authorize(JSON.parse(content), showAccessToken);
});
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, content) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(content));
callback(JSON.parse(content))
oAuth2Client.on('tokens', (tokens) => {
//this handle not work
if (tokens.refresh_token) {
// store the refresh_token in my database!
console.log(tokens.refresh_token);
}
console.log(tokens.access_token);
});
});
}
/**
* This method opens a new window to let users log-in the OAuth provider service,
* grant permissions to OAuth client service (this application),
* and returns OAuth code which can be exchanged for the real API access keys.
*
* #param {*} interactionWindow a window in which the user will have interaction with OAuth provider service.
* #param {*} authPageURL an URL of OAuth provider service, which will ask the user grants permission to us.
* #returns {Promise<string>}
*/
function getOAuthCodeByInteraction(interactionWindow, authPageURL) {
interactionWindow.loadURL(authPageURL, { userAgent: 'Chrome' });
return new Promise((resolve, reject) => {
const onclosed = () => {
reject('Interaction ended intentionally ;(');
};
interactionWindow.on('closed', onclosed);
interactionWindow.on('page-title-updated', (ev) => {
const url = new URL(ev.sender.getURL());
// console.log(url.searchParams)
if (url.searchParams.get('approvalCode')) {
console.log('allow')
interactionWindow.removeListener('closed', onclosed);
interactionWindow.close();
return resolve(url.searchParams.get('approvalCode'));
}
if ((url.searchParams.get('response') || '').startsWith('error=')) {
console.log('reject')
interactionWindow.removeListener('closed', onclosed);
interactionWindow.close();
return reject(url.searchParams.get('response'));
}
});
});
};
function executeAuthWindow(authWindow, authUrl) {
authWindow.setMenu(null);
authWindow.show();
return new Promise((resolve, reject) => {
getOAuthCodeByInteraction(authWindow, authUrl)
.then((res) => {
if (res != 'Interaction ended intentionally ;(') {
return resolve(res);
}
if (res == 'Interaction ended intentionally ;(') {
return reject('Fail:Authorization window colose');
}
}).catch((err) => {
if (err = 'error=access_denied') {
return reject('Fail: error=access_denied');
}
});
})
}
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
const authWindow = new BrowserWindow({
width: 600,
height: 800,
show: false,
'node-integration': false,
'web-security': false
});
executeAuthWindow(authWindow, authUrl)
.then((code) => {
//access_token: and refresh_token:
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
console.log('getToken')
console.log(token)
oAuth2Client.setCredentials(token);
console.log(oAuth2Client)
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
});
}).catch((err) => {
console.log(err)
})
}
// initOAuthClient
function showAccessToken(token) {
console.log(token)
}
}
credentials file
{
"installed": {
"client_id": "*******17079-*********gjlh6g2nnndhqotn3ij509k.apps.googleusercontent.com",
"project_id": "quickstart-**********",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "*********dNz3Gceo9F",
"redirect_uris": [
"urn:ietf:wg:oauth:2.0:oob",
"http://localhost"
]
}
}

Related

SimpleGraphClient: Invalid token received

I started developing a new MS Teams Application and I am trying to authenticate a MS Teams user on my app's backend by following the source code of
https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/app-sso
But unfortunately when I am trying to create a SimpleGraphClient with the token acquired by this function
// Get Access Token
const getAccessToken = async(req) => {
return new Promise((resolve, reject) => {
const { tenantId, token } = reqData(req);
const scopes = ['User.Read']; //['User.Read', 'email', 'offline_access', 'openid', 'profile'];
const url = `https://login.microsoftonline.com/${ tenantId }/oauth2/v2.0/token`;
const params = {
client_id: process.env.MicrosoftAppId,
client_secret: process.env.MicrosoftAppPassword,
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: token,
requested_token_use: 'on_behalf_of',
scope: scopes.join(' ')
};
// eslint-disable-next-line no-undef
fetch(url, {
method: 'POST',
body: querystring.stringify(params),
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(result => {
if (result.status !== 200) {
result.json().then(json => {
// eslint-disable-next-line prefer-promise-reject-errors
reject({ error: json.error });
});
} else {
result.json().then(async json => {
resolve(json.access_token);
});
}
});
});
};
I am taking the exception :
throw new Error('SimpleGraphClient: Invalid token received.');
What am I doing wrong?

NestJS Swagger oAuth2 (oAuth0) to bearer token in header

this is my code:
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '#nestjs/swagger';
import * as config from 'config';
import * as crypto from 'crypto';
async function bootstrap() {
const serverConfig = config.get('conf');
console.log(`Environment:` + serverConfig.env);
console.log(`Running Port:` + serverConfig.server.port);
const teanat = 'MY_AUTH0_DOMAIN'
const app = await NestFactory.create(AppModule);
const nonce = crypto.randomBytes(16).toString('base64');
const docBuilderConfig = new DocumentBuilder()
.setTitle('Awesome Middleware')
.setDescription('Represents the middleware api services')
.setVersion('1.0')
.addOAuth2(
{
type: 'oauth2',
flows: {
implicit: {
tokenUrl: `${teanat}/oauth/token`,
authorizationUrl: `${teanat}/authorize?audience=${`${teanat}/api/v2/`}&nonce=${nonce}`,
scopes: {} // { openid: openid, ... }
},
},
},
)
.build()
const document = SwaggerModule.createDocument(app, docBuilderConfig);
const swaggerCustomOptions: SwaggerCustomOptions = {
'customSiteTitle': 'Middle Api',
'explorer': true,
'swaggerOptions': {
persistAuthorization: true,
oauth2RedirectUrl: 'http://localhost:4200/',
oauth: {
clientId: 'CLIENT_ID',
}
}
}
SwaggerModule.setup('/', app, document, swaggerCustomOptions);
app.enableCors();
app.use((req, res, next) => {
res.header("X-powered-by", "My Company");
res.header("Server", "My Server");
next();
});
await app.listen(4200);
}
bootstrap();
I go to the login page just like in the client web app
and then the token return on the URL prams which I redirect to
(localhost:4200?token_access=MY_YOKEN)
I want this token to be injected to the header where the control use this auth2 with the same auth name

Loopback 4 implementing Microsoft Graph API

I am currently building a microservice that is responsible to communicate with Microsoft Graph, I have already made one with Loopback 3 and this was not a problem.
Except now, I am trying to do the same thing but with Loopback 4, but since the language changes from JavaScript to TypeScript I don't know if it's still possible to achieve this.
This was the code I used for Loopback 3 in my root server file:
'use strict';
const express = require('express');
const erouter = require('express').Router();
var session = require('express-session');
var passport = require('passport');
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
const request = require('request');
var querystring = require('querystring');
const graph = require('./graph.service');
const getBookings = require('./getBookings.service');
const cors = require('cors');
var compression = require('compression');
module.exports = function(server) {
// Install a `/` route that returns server status
var router = server.loopback.Router();
router.get('/', server.loopback.status());
// Configure simple-oauth2
const oauth2 = require('simple-oauth2').create({
client: {
id: process.env.OAUTH_APP_ID,
secret: process.env.OAUTH_APP_PASSWORD
},
auth: {
tokenHost: process.env.OAUTH_AUTHORITY,
authorizePath: process.env.OAUTH_AUTHORIZE_ENDPOINT,
tokenPath: process.env.OAUTH_TOKEN_ENDPOINT
}
});
passport.serializeUser(function(user, done) {
var MSUser = server.models.MSUser;
var id = user.profile.oid;
MSUser.find({ where: { oid: id } }, function(err, msu) {
if (err) return done(err, null);
if (!msu) {
MSUser.create(user);
} else {
done(null, id);
}
});
});
passport.deserializeUser(function(id, done) {
var MSUser = server.models.MSUser;
MSUser.findById(id, function(err, user) {
if (err) return next(err);
done(null, user);
});
});
async function signInComplete(iss, sub, profile, accessToken, refreshToken, params, done) {
if (!profile.oid) {
return done(new Error("No OID found in user profile."), null);
}
try {
const user = await graph.getUserDetails(accessToken);
if (user) {
profile['email'] = user.mail ? user.mail.toLowerCase() : user.userPrincipalName.toLowerCase();
}
} catch (err) {
done(err, null);
}
let oauthToken = oauth2.accessToken.create(params);
var AuthUser = server.models.AuthUser;
var user = {};
AuthUser.find({ where: { email: profile['email'] } }, function(err, au) {
if (err) return done(err, null);
if (au.length != 1) return done(new Error("User was not found with that email address."), null);
user = au[0];
const dataMsAuth = querystring.stringify({
"created": new Date().toDateString(),
"token_type": oauthToken.token.token_type,
"expires_in": oauthToken.token.expires_in,
"access_token": oauthToken.token.access_token,
"scope": oauthToken.token.scope,
"ext_expires_in": oauthToken.token.ext_expires_in,
"refresh_token": oauthToken.token.refresh_token,
"id_token": oauthToken.token.id_token,
"expires_at": new Date(oauthToken.token.expires_at).toDateString()
});
const postMSAuth = {
url: process.env.API_URL + "api/Companies/" + user.companyId + "/msauth",
method: 'POST',
body: dataMsAuth,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
request(postMSAuth, function(err, resp, body) {
if (err) return done(err, null);
var MSUser = server.models.MSUser;
var id = profile.oid;
var msuser = { profile, oauthToken, oid: id, email: profile.email }
MSUser.findById(id, function(err, msu) {
if (err) return done(err, null);
if (!msu) {
MSUser.create(msuser);
}
});
return done(null, msuser);
});
});
}
passport.use(new OIDCStrategy({
identityMetadata: `${process.env.OAUTH_AUTHORITY}${process.env.OAUTH_ID_METADATA}`,
clientID: process.env.OAUTH_APP_ID,
responseType: 'code id_token',
responseMode: 'form_post',
redirectUrl: process.env.OAUTH_REDIRECT_URI,
allowHttpForRedirectUrl: true,
clientSecret: process.env.OAUTH_APP_PASSWORD,
validateIssuer: false,
passReqToCallback: false,
scope: process.env.OAUTH_SCOPES.split(' ')
},
signInComplete
));
var app = express();
app.use(compression());
app.use(session({
secret: process.env.BOOKINGS_LOOPBACK_SECRET,
resave: false,
saveUninitialized: false,
unset: 'destroy'
}));
app.use("/result", express.static('client'));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors({
origin: '*'
}));
erouter.get('/API/bookings/:companyid', getBookings());
erouter.get('/auth/signin',
function(req, res, next) {
passport.authenticate('azuread-openidconnect', {
response: res,
prompt: 'login',
state: req.query.state,
failureRedirect: process.env.WEBSITE_URL + 'settings?error=incorrect_request',
successRedirect: process.env.WEBSITE_URL + 'settings?auth=success'
})(req, res, next);
}
);
erouter.post('/auth/callback',
function(req, res, next) {
passport.authenticate('azuread-openidconnect', {
response: res,
failureRedirect: process.env.WEBSITE_URL + 'settings?error=permission_denied',
successRedirect: process.env.WEBSITE_URL + 'settings?auth=success'
})(req, res, next);
}
);
app.use(erouter);
server.use(app);
server.use(router);
};
So my question is: "Is it possible to implement Microsoft Graph API in TypeScript using Loopback 4 or should I keep using Loopback 3 In JavaScript?"
Thanks in advance,
Billy Cottrell

zapier oauth2 giving error for access_token

Hi i am trying to implement oauth2 with Zapier platform, and i am facing some error.
I just have a testing node.js app where i am login request from zapier platform.
const express = require('express');
const jwt = require('jsonwebtoken');
const uuidv4 = require('uuid/v4');
const app = express();
const port = process.env.PORT || 3000;
let count = 0;
// const redirect url = https://zapier.com/dashboard/auth/oauth/return/App77581CLIAPI/
app.get('/', (req, res) => res.json({
count
}));
app.post('/zapier', (req, res) => {
count++;
res.json({
count
})
})
app.get('/api/login', (req, res) => {
// console.log('req', req);
const { state } = req.query;
// const state = 1578212496.13945431311;
console.log(state);
const user = {
name: "ankit",
id: 12344
}
const token = jwt.sign(user, "luciferTonify");
const code = uuidv4();
res.redirect(`https://zapier.com/dashboard/auth/oauth/return/App77581CLIAPI?code=${code}&state=${state}&access_token=${code}&token_type=bearer`);
})
app.post('/api/login', (req, res) => {
const user = {
name: "ankit",
id: 12344
}
const token = jwt.sign(user, "luciferTonify");
res.json({ token });
});
function verifyToken(req, res, next) {
const { authorization: token } = req.headers;
if (typeof token == 'undefined' || !token) {
req.token = token;
next();
} else {
res.sendStatus(403);
}
}
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
its just a testing app, so i am trying to send an access_token, but zapier is giving me this error
**
Zapier could not connect to your account. Field named access_token
not found in OAuth2 results
**
?
can anyone help me what i am doing wrong ?

OAuth2 in electron application in current window

I'm trying to implement OAuth2 authentication in Angular 2 ( Electron ) application.
I achieve that on the way with a popup that is called after user click on 'Sign In' button.
In popup user types their credentials and allows the access and on confirm code is returned and I'm able to catch redirect request which I can't do without popup.
Here is implementation that works:
return Observable.create((observer: Observer<any>) => {
let authWindow = new electron.remote.BrowserWindow({ show: false, webPreferences: {
nodeIntegration: false
} });
authWindow.maximize();
const authUrl = AUTHORIZATION_WITH_PROOF_KEY_URL
+ `?client_id=${CLIENT_ID}&response_type=code&scope=api_search&`
+ `redirect_uri=${REDIRECT_URL}&code_challenge=${challenge}&code_challenge_method=S256`;
if (this.clearStorage) {
authWindow.webContents.session.clearStorageData({}, () => {
this.clearStorage = false;
authWindow.loadURL(authUrl);
authWindow.show();
});
} else {
authWindow.loadURL(authUrl);
authWindow.show();
}
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
const code = this.getCode(newUrl, authWindow);
if (!code) {
this.clearStorage = true;
return;
}
this.requestToken({
grant_type: 'authorization_code',
code: code,
code_verifier: verifier,
redirect_uri: REDIRECT_URL
})
.subscribe((response: { access_token: string, refresh_token: string }) => {
observer.next(response);
});
});
// Reset the authWindow on close
authWindow.on('close', () => {
authWindow = null;
});
});
and as you can see in above code I'm creating new BrowserWindow with:
new electron.remote.BrowserWindow({ show: false, webPreferences: {
nodeIntegration: false
} });
and with that approach I'm able to catch up redirect request with a block of code that starts with:
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
....
}
but I'm not able to solve this without popup ( modal ).
Here is my attempt:
return Observable.create((observer: Observer<any>) => {
let authWindow = electron.remote.getCurrentWindow();
const authUrl = AUTHORIZATION_WITH_PROOF_KEY_URL
+ `?client_id=${CLIENT_ID}&response_type=code&scope=api_search&`
+ `redirect_uri=${REDIRECT_URL}&code_challenge=${challenge}&code_challenge_method=S256`;
if (this.clearStorage) {
authWindow.webContents.session.clearStorageData({}, () => {
this.clearStorage = false;
authWindow.loadURL(authUrl);
});
} else {
authWindow.loadURL(authUrl);
}
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
debugger;
// this is not called, I'm not able to catch up redirect request
});
// Reset the authWindow on close
authWindow.on('close', () => {
authWindow = null;
});
});
With my approach I get login screen from remote URL in a current window, but the problem is that I'm not able to catch redirect request with ('did-get-redirect-request') event.
I also tried with 'will-navigate' and many others.
Although I don't have a direct answer I thought I'd point you to Google's AppAuth-JS libraries, which cover OAuth based usage for Electron Apps.
My company have used AppAuth libraries for the mobile case and they worked very well for us, so that we wrote less security code ourselves and avoided vulnerabilities.
There is also an Electron Code Sample.

Resources