Firebase functions '__session' cookie stopped working - node.js

I am using firebase functions for instagram authentication for my android app.
This is my flow:
User access my "connect" api with param that states its user name (..... ?username=someusername)
"Connect" saves the username as a cookie called "__session" (as this is the only cookie allowed by functions).
"connect" redirects 'https://api.instagram.com' which asks the user for permission to give me their details.
After user agrees, Instagram redirects to my function called "onredirect"
I store the user token in realtime data base along side with the user name found in "__session" cookie.
All of this worked all in well for a month or two but suddenly "redirect" function can no longer retrieve the "__session" cookie (it's empty).
I'm not sure what has changed or possibly what im doing wrong (it used to work and the code has not changed), would be happy for you help.
Code:
exports.connectinsta = functions.https.onRequest(async (req, res) => {
functions.logger.log('connectinsta query:', req.query);
functions.logger.log('connectinsta query:', req.query.account);
if(!req.query.account){
res.send("No user account provided in query")
} else {
cookieParser()(req, res, () => {
const oauth2 = instagramOAuth2Client2();
res.setHeader('Cache-Control', 'private');
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI_Y,
scope: OAUTH_SCOPES,
client_id: functions.config().instagram.client_id,
});
res.cookie('__session', req.query.account, {
maxAge: 3600000,
secure: true,
httpOnly: true,
});
res.redirect(redirectUri)
});
}
});
exports.onredirect = functions.https.onRequest(async (req, res) => {
try {
cookieParser()(req, res, async () => {
functions.logger.log('onredirect cookies:', req.cookies);
functions.logger.log('onredirect cookies session:', req.cookies.__session);
const session_id = req.cookies.__session <--- NO LONGER WORKS
const oauth2 = instagramOAuth2Client();
const results = await oauth2.authorizationCode.getToken({
client_id: functions.config().instagram.client_id,
client_secret: functions.config().instagram.client_secret,
grant_type: "authorization_code",
code: req.query.code,
redirect_uri: OAUTH_REDIRECT_URI_Y
});
const accessToken = results.access_token;
functions.logger.log('EExperiment:', accessToken);
var result_long = await get_long_token_promise(accessToken)
functions.logger.log('Long EExperiment:', result_long.access_token);
save_code(session_id, result_long.access_token)
res.send("Success! Return to app")
});
} catch(error) {
functions.logger.error("onredirect error1", error)
return res.jsonp({
error: error.toString(),
});
}
});

Related

How to get accestoken for Subscribe With Google when using Sign In With Google?

I have to switch from 'Google Sign In' to 'Sign In With Google' (SIWG).
I use passport.js with passport-google-oidc. SIWG works, I ask for the scopes openid, profile and email, and I get it.
But SIWG does not give me any access token as it did before Google Sign In. I need this access token to continue with 'Subscribe With Google' (SWG).
Is there any scope that gives it to me?
As I didn't get from SIWG any access token but at least a 'code' I tried to work with this code.
With google-auth-library and the code, I tried to get an access token. With this library, I use the same id, secret and callback url as with SIWG.
But oAuth2Client.getToken(code) only gives me {"error": "invalid_grant", "error_description": "Bad Request"}
Any idea how to make the request go through ok?
` // 1. Defining strategy
const GoogleStrategy = require('passport-google-oidc');
const Config_Account_Passport = {
// "google" will refer to strategy passport-google-oidc SIWG
Google : new GoogleStrategy({
clientID : '6...MyClientID0c...apps.googleusercontent.com',
clientSecret : 'a...MyClientSecret...',
callbackURL: "https://...UrlOfMyServer.../google/auth",
passReqToCallback : true
}, async function(req, issuer, profile, done) {
// verified function
try {
// get code
const code = req.query.code;
// ...
// get data for login of user. Store data in database if new user.
const email = profile.emails[0].value;
const oauth_id = profile.id;
// Look for user in database. If new store. In Both cases: Having a user object.
//...
// AND now to entitlements (part 4 below)
await checkEntitlements(user, code);
// following never executed because checkEntitlements fails -> goes to catch
// Everything is fine
return done(null, user)
} catch(e) {
return done(true, false); // error and no user
}
})
}
module.exports = Config_Account_Passport;
// 2. Applying strategy
//...
passport.authenticate('google', { scope: ['openid','profile','email','https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly'], accessType: 'offline'});
// 3. Google calls callbackURL -> Successful verified
//...
function(req, res, next) {
// in callback from google
passport.authenticate('google', {}, function (err, user) {
// verified done
if (err) {
// done(true, ...) was called
// user is stays NOT logged id
return res.redirect('/user/login');
}
return res.redirect('/user/loggedin');
})
}
// 4. Check entitlements
// First get access token from code
const {OAuth2Client} = require('google-auth-library');
function getAccessToken(code) {
return new Promise(async (resolve, reject) => {
try {
const oAuth2Client = new OAuth2Client(
clientID : '6...MyClientID0c...apps.googleusercontent.com',
clientSecret : 'a...MyClientSecret...',
callbackURL: "https://...UrlOfMyServer.../google/auth",
);
// Generate the url that will be used for the consent dialog.
await oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: ['https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly'],
});
const {tokens} = await oAuth2Client.getToken(code);
// following never executed because getToken fails -> goes to catch
// "error": "invalid_grant"
//...get access token from tokens
resolve (token);
} catch (e){
return reject(e);
}
})
}
const Payment_SwG = require('./../modules/Payment/SwG');
// Then Get Entitlement with access token
async function checkEntitlements(user, code) {
return new Promise(async (resolve, reject) => {
try {
// first get access token by using code
const accesstoken = await getAccessToken(code);
// following never executed because getAccessToken fails -> goes to catch
// get entitlements by access token
const entitlements = await Payment_SwG.checkEntitlements(accesstoken);
//...
} catch(e) {
return reject(e);
}
})
}`
I tried different scopes. I examined all parameters I got back from SIWG, looking out for an access token.
The Scopes you see in the program code are accepted by SIWG. Execution stops after successful SIWG when I try to get an access token.

Oauth2 with Google docs API Node.js (trying to programmatically write a new google doc)

I have a typical web app with a client and a node.js server. When a user selects an option on the client, I want to export (create) a google doc in their drive.
I am half way there following this tutorial https://developers.google.com/identity/protocols/oauth2/web-server
With my current set up, after the user authenticates, the authentication token is sent to a web hook (server side), but I don't have any of the data for creating the google doc there.
If I did, I could create the doc from there. Otherwise, I need to send the token itself back to the client so I can create the doc with the necessary payload from there.
In that case, I don't know how to signal to the client that the user has been authenticated. Should I use a web socket?
Something tells me that my general set up might not be correct, and that I should be doing it a different way in my use case.
This is my client side code that brings the user to the google auth page after getting the auth url (not sure if this really needs to be done server side, but since I have some user credentials I thought it might be better).
export async function exportToGoogleDoc() {
const response = await POST(`${API_URL}export/gdoc`, {
'hello': 'world'
});
if (response.status == 200){
window.location.href = response.authUrl;
}
}
And then the endpoint (just returns the autheticationUrl)
api.post('/export/gdoc', express.raw({ type: 'application/json' }), async (req, res, next) => {
try {
const scopes = [
'https://www.googleapis.com/auth/drive'
];
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
const authorizationUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
include_granted_scopes: true
});
res.json({ 'status': 200, authUrl : authorizationUrl } );
} catch (err){
next(err);
}
});
But then after the user agrees and authenticates with their google account, the auth token is sent to this web hook. At the bottom I am able to write an empty google doc to the authenticated google drive, but I don't have the data I need to create the real doc.
api.get('/auth/google', express.raw({ type: 'application/json' }), async (req, res, next) => {
const q = url.parse(req.url, true).query;
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
if (q.error) {
console.log('Error:' + q.error);
} else {
const { tokens } = await oauth2Client.getToken(q.code.toString());
oauth2Client.setCredentials(tokens);
const drive = google.drive('v3');
const requestBody = {
'name': 'Dabble',
'mimeType': 'application/vnd.google-apps.document',
};
drive.files.create({
requestBody: requestBody,
fields: 'id',
auth: oauth2Client
}, function (err, file) {
if (err) {
// Handle error
console.error(err);
} else {
console.log('File Id: ', file);
}
});
}
Somehow I either need to get the data for the google doc inside this web hook, or to listen for this web hook from the client.
OR I need an entirely different set up. I realize I also should be probably storing this token somewhere (local storage on client side?) and only making this call if they do not have a token.
Can anyone help me modify my set up? Thanks!

No refreshToken in msal-node when providing scope "offline-access"

I hope you can help me: I currently develop an app which needs access to the users calendar (outlook-calendar) to find free meeting slots (other users will be able to see and then select one of the free slots - similar to calendly). For that I use msal-node to authenticate against azureAD. But my use case needs "everytime"-access to the calendars from all users. This is why I want to get an refresh_token. The docs of msal-node say that I should provide the offline_scope to get an refreshtoken while doing the OAuth-process.
My problem is that I receive an access_token and id_token and so on, but no refreshtoken. The Azure-response further shows a successful answer but when I take a look into the returned scopes I cannot find offline_scope.
You can see the returned scopes here
What should I do?
I use a cofidentalClientApplication msal-node instance:
const oauth2Client = MicrosoftClient.Connection
const authCodeUrlParameters = {
scopes: ["offline_access", "user.read"],
forceRefresh: true,
redirectUri: "http://localhost:3000/outlookRedirect",
}
try {
console.log("GDFHGJF")
return oauth2Client.getAuthCodeUrl(authCodeUrlParameters)
}
After receiving the code from Azure, I process it via:
const oauth2Client = MicrosoftClient.Connection
const tokenRequest = {
code: code,
scopes: ["user.read", "offline_access"],
forceRefresh: true,
redirectUri: "http://localhost:3000/outlookRedirect",
//client_secret: process.env.MICROSOFTCLIENTSECRET,
}
const testus = await oauth2Client.acquireTokenByCode(tokenRequest)
const tokenRequest2 = {
scopes: ["user.read", "offline_access"],
forceRefresh: true,
redirectUri: "http://localhost:3000/outlookRedirect",
account: testus.account,
}
oauth2Client
.acquireTokenSilent(tokenRequest2)
.then((response) => {
console.log("\nResponse: \n:", response)
})
.catch((error) => {
console.log(error)
})
return
What is my fault? I appreciate any kind of help!
Thank you in advance,
Lukas
after calling 'acquireTokenByCode' , 'pca' now has the refresh token. const tokenCache = pca.getTokenCache().serialize(); const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
Below is a complete snippet of How to get the Refresh and Access token.
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const express = require("express");
const msal = require('#azure/msal-node');
const SERVER_PORT = process.env.PORT || 3000;
const REDIRECT_URI = "http://localhost:3000/redirect";
// Before running the sample, you will need to replace the values in the config,
// including the clientSecret
const config = {
auth: {
clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
authority: "https://login.microsoftonline.com/84fb56d3-e15d-4ae1-acd7-cbf83c4c0af3",
clientSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
         piiLoggingEnabled: false,
         logLevel: msal.LogLevel.Verbose,
        }
    }
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
app.get('/', (req, res) => {
const authCodeUrlParameters = {
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
prompt:'consent'
};
// get url to sign user in and consent to scopes needed for application
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
res.redirect(response);
}).catch((error) => console.log(JSON.stringify(error)));
});
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ["user.read","offline_access"],
redirectUri: REDIRECT_URI,
accessType: 'offline',
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
const accessToken = response.accessToken;
const refreshToken = () => {
const tokenCache = pca.getTokenCache().serialize();
const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken
const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
return refreshToken;
}
const tokens = {
accessToken,
refreshToken:refreshToken()
}
console.log(tokens)
res.sendStatus(200);
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
});
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
msal-node does not expose the refresh token to the end user by design. It is stored and used internally under the hood when you need a new access token. You should call acquireTokenSilent each time you need an access token and msal-node will manage the tokens by either returning a cached token to you or using the refresh token to acquire a new access token.
For more context: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/2836
In addition to the accepted answer, its important to note that the MSAL cache can have many authenticated users (and lots of refresh tokens). Here is my solution to extract the exact refresh token for a specific user.
I use this on each login to exact the individuals refresh token and store it.
public extractRefresh = ( homeAccountId : string ) : string =>
{
try
{
const tokenCache = this.msalClientApp.getTokenCache().serialize();
const refreshTokenObject = ( JSON.parse( tokenCache ) ).RefreshToken;
let refreshToken = '';
Object.entries( refreshTokenObject ).forEach( ( item : any ) =>
{
if ( item[1].home_account_id === homeAccountId )
{
refreshToken = item[1].secret;
}
});
return refreshToken;
}
catch
{
return '';
}
}

problems trying to use the Instagram api

I am working with Angular and Node.js with Express.js. I have all day trying to get the posts from my Instagram account but I have not even been able to get the API token. I understand that you first have to make a request to this URL: https://api.instagram.com/oauth/authorize?client_id=[instagram-app-id-lex.europa.eu&redirect_uri=[redirect-uriíritu&scope=[scope-lex.europa.eu&response_type=code&state = {state} (replacing the id and redirect-uri obviously) but I get no response. I already created my app in the instagram api following its documentation and add the tester and accept the authorization from the account settings.
At the moment I have this code en node.js:
const instagram = require('../controllers/instagram/instagram.controller');
const route = '/auth'
module.exports= (app,db, protegerRutas)=> {
app.get(`${route}/instagram`, (req, res)=> instagram.auth(req, res));
app.get(`/handle`, (req,res)=> instagram.handleauth(req,res));
// app.get(`${}`)
// app.post(`${route}/actualizar`, protegerRutas, (req, res)=> actualizar.actualizarDatos(req, res, db))
}
controller:
const Instagram = require('node-instagram').default;
const axios = require('axios');
const clavesInstagram = require('../../config/config').instagram;
const {clientId, clientSecret}= clavesInstagram;
const urlInstagram = 'https://api.instagram.com/oauth/authorize?client_id=201577004938695&redirect_uri=https://localhost:3008/auth/handle&scope={scope}&response_type=code';
const redirectUri = 'https://b4ae544459e3.ngrok.io/handle';
const instagram = new Instagram({
clientId: clientId,
clientSecret: clientSecret
})
module.exports= {
auth: (req,res)=> {
axios.get(urlInstagram)
.then( res => {
console.log('\n\nRespuesta: ',res);
})
.catch( err => {
console.log('\n\nError: ',err);
})
},
// auth: (req, res)=> {
// console.log('\n\nAuth Controller...')
// res.redirect(
// instagram.getAuthorizationUrl(redirectUri, {
// scope: ['basic','likes'],
// state: 'your state'
// })
// )
// },
handleauth: async (req, res)=> {
try {
console.log('\n\n\n\nHandleAuth....\n\n\n\n\n')
const code = req.query.code; //código devuelto por Instagram
const data= await instagram.authorizeUser(code, redirectUri);
console.log('\n\nData Instagram:', data)
res.json(data);
} catch(e) {
// statements
console.log(e);
res.json(e)
}
}
}
I understand that in the instagram API configuration the URL of my app is added to which they redirect and send the token, the url of my local app is something like this: http://localhost:3008/ and the path to which I want instagram to redirect would be this: https://localhost:3008/auth/handle (I must put it with 'https' in instagram because it does not allow http, so I don't know if this would include me). The problem is that with this code I am not getting the token that should be sent to my route https://localhost:3008/auth/handle. What am I doing wrong? I've been at this for many hours and honestly I can't find a solution. I read the documentation and try to do what it says but I can't do it. Could someone help me with this? I would be enormously grateful.
Thanks in advance.

Passport & JWT & Google Strategy - Disable session & res.send() after google callback

Using: passport-google-oauth2.
I want to use JWT with Google login - for that I need to disable session and somehow pass the user model back to client.
All the examples are using google callback that magically redirect to '/'.
How do I:
1. Disable session while using passport-google-oauth2.
2. res.send() user to client after google authentication.
Feel free to suggest alternatives if I'm not on the right direction.
Manage to overcome this with some insights:
1. disable session in express - just remove the middleware of the session
// app.use(session({secret: config.secret}))
2. when using Google authentication what actually happens is that there is a redirection to google login page and if login is successful it redirect you back with the url have you provided.
This actually mean that once google call your callback you cannot do res.send(token, user) - its simply does not work (anyone can elaborate why?). So you are force to do a redirect to the client by doing res.redirect("/").
But the whole purpose is to pass the token so you can also do res.redirect("/?token=" + token).
app.get( '/auth/google/callback',
passport.authenticate('google', {
//successRedirect: '/',
failureRedirect: '/'
, session: false
}),
function(req, res) {
var token = AuthService.encode(req.user);
res.redirect("/home?token=" + token);
});
But how the client will get the user entity?
So you can also pass the user in the same way but it didn't felt right for me (passing the whole user entity in the parameter list...).
So what I did is make the client use the token and retrieve the user.
function handleNewToken(token) {
if (!token)
return;
localStorageService.set('token', token);
// Fetch activeUser
$http.get("/api/authenticate/" + token)
.then(function (result) {
setActiveUser(result.data);
});
}
Which mean another http request - This make me think that maybe I didnt get right the token concept.
Feel free to enlighten me.
Initialize passport in index.js:
app.use(passport.initialize());
In your passport.js file:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
'http://localhost:3000/auth/google/redirect',
},
async (accessToken, refreshToken, profile,
callback) => {
// Extract email from profile
const email = profile.emails![0].value;
if (!email) {
throw new BadRequestError('Login failed');
}
// Check if user already exist in database
const existingUser = await User.findOne({ email
});
if (existingUser) {
// Generate JWT
const jwt = jwt.sign(
{ id: existingUser.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update existing user
existingUser.token = jwt
await existingUser.save();
return callback(null, existingUser);
} else {
// Build a new User
const user = User.build({
email,
googleId: profile.id,
token?: undefined
});
// Generate JWT for new user
const jwt = jwt.sign(
{ id: user.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update new user
user.token = jwt;
await auth.save();
return callback(null, auth);
}
}));
Receive this JWT in route via req.user
app.get('/google/redirect', passport.authenticate('google',
{failureRedirect: '/api/relogin', session: false}), (req, res) => {
// Fetch JWT from req.user
const jwt = req.user.token;
req.session = {jwt}
// Successful authentication, redirect home
res.status(200).redirect('/home');
}

Resources