How can I share data between routes on the request object in Express? - express

I'm working on an application that authenticates with Spotify's API. I am using passport-spotify to do so. I need to be able to access a session ID a my root route (/).
While I'm able to set the session id during the /callback after authentication with Spotify, I can't then access the session id at /. Can someone please explain to me how to pass data between routes in Express so that I can access req.session.id in / after I've authenticated?
I'll share my endpoints here:
/
app.get('/', cors(), (req, res) => {
if (req.session.id != null) {
res.json({isAuthenticated: true })
} else {
res.json({isAuthenticated: false, message: 'Please log in.' })
}
})
Passport Strategy
passport.use(new SpotifyStrategy({
clientID: clientId,
clientSecret: clientSecret,
callbackURL: CALLBACK_URL
},
(accessToken, refreshToken, profile, done) => {
process.nextTick(function () {
let user = { spotifyId: profile.id, access_token: accessToken,
refresh_token: refreshToken }
return done(null, user)
})
}))
/auth/spotify
app.get('/auth/spotify',
passport.authenticate('spotify', {scope: ['user-read-email', 'user-
read-private'], showDialog: true}),
(req, res) => {
})
/callback
app.get('/callback', passport.authenticate('spotify', {
failureRedirect: '/', successRedirect: FRONTEND_URL }), (req, res,
next) => {
req.session.id = req.user.spotifyId
localStorage.setItem('access_token_' + req.session.id,
req.user.access_token)
localStorage.setItem('refresh_token_' + req.session.id,
req.user.refresh_token)
return next(null, req.session.id)
})

There is no way to share data in the way you asked your question, and the reason thereof is very simple, they are 2 different req Objects.
The what you can do, is define a separate middlware function, something like this :
function middlware(req, res, next) {
req.session.id = req.user.spotifyId;
next();
}
And you call that function in both of your routs as a middlware after the Spotify middlware.

Related

How to send access_token as cookie to client in NextJs(fullframework) + Passport

First of all, please understand that I am not good at English.
I am currently implementing authentication using NextJS (full framework) + passport.
I succeeded in getting the user's Twitter information using strategy, and after that, I'm going to jwt the user's information in db and deliver it to you as a cookie, but I don't know when and where this should be done.
code is like
lib/passport.ts
passport.use(
new TwitterStrategy(
{
consumerKey: process.env.TWITTER_CONSUMER_KEY as string,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET as string,
callbackURL: '/api/auth/callback/twitter',
includeEmail: true,
},
async (_accessToken, _refreshToken, profile: TwitterProfile, cb: any) => {
try {
return cb(null, profile);
} catch (e: any) {
throw new Error(e);
}
}
)
);
req.session.passport
passport.serializeUser((user, cb) => {
process.nextTick(function () {
return cb(null, user);
});
});
passport.deserializeUser(function (
user: any,
cb: (arg0: null, arg1: any) => any
) {
process.nextTick(function () {
return cb(null, user);
});
});
middleware/auth.ts
const auth = nextConnect()
.use(
session({
secret: process.env.NEXTAUTH_SECRET,
cookie: {
maxAge: 60 * 60 * 8, // 8 hours,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
},
resave: false,
saveUninitialized: true,
})
)
.use((req, res, next) => {
// Initialize mocked database
// Remove this after you add your own database
req.session.users = req.session.users || [];
next();
});
api/auth/callback/twitter.ts
import nextConnect from 'next-connect';
import passport from '#/lib/passport-twitter';
import auth from '#/middleware/auth';
const handler = nextConnect();
handler
.use(auth)
.use(passport.session())
.get((req, res) => {
passport.authenticate('twitter', async () => {
// I tried to set header here
res.setHeader('Set-Cookie', 'test');
res.end();
});
});
And when I try to log in additionally, where can I check the client request's cookies? (for access_token verification).
Lack of knowledge may lead to a lack of explanation and a wrong approach.
I want to use authentication information to store user information according to the situation. For example, if you have another account stored in the same email (Facebook), you can send a specific message without creating a user.

Authentication for protected routes using express/jwt

When the user logins, the access token is created and sent to the user, it is then stored in sessionStorage. Everything before this works fine. My problem is that I do not know how to use the access token to gain access to protected routes.
express app.js (smoothies is the protected route)
app.get('/smoothies', requireAuth, (req, res) => res.render('smoothies'));
authMiddleware.js
const User = require('../models/User');
const requireAuth = (req, res, next) => {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
// check json web token exists & is verified
if (token) {
jwt.verify(token, 'night of fire', (err, decodedToken) => {
if (err) {
console.log(err.message);
res.redirect('/login?err=auth');
} else {
console.log(decodedToken);
next();
}
});
} else {
res.redirect('/login?err=auth');
}
};
// check current user
module.exports = { requireAuth };
smoothies.ejs
var myHeaders = new Headers();
myHeaders.append("Authorization", `Bearer ${token}`);
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("http://localhost:3000/smoothies", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
//Should I be even doing this fetch GET request on smoothie.ejs?
})
Smoothies is the protected route. When I try to use Postman and send a GET request to (/smoothies) using authorization : bearer token, it works and I am able to access /smoothies. However, if I try on the real application, I was denied access even with access token in my sessionStorage. When I console.log req.headers['authorization'], it was undefined so I am guessing my GET request from smoothie.ejs does not work. Does anyone know what is the solution?

setting cookie after google passport callback

I am using facebook and google oauth2 login using passport js, with this flow
User clicked the login button
Redirects to facebook/google auth page (depending on what login the user chooses)
The auth page redirects back to a callback page (/auth/callback/[provider])
A passport express middleware will catch it to parse some data and then send it to a remote api of myown to sign the user in
The auth remote api will send a response back consisting the user token
A custom express middleware will catch the response to set cookie on the server
the express chain ends by route it to /profile (cookie with token is set on the browser)
/profile will then checks if there is a token, if there is not: it will redirect to /
Doing this flow on facebook login is fine, the user is successfully redirected to /profile, with all of its data and token, the google oauth2 login however seems to be doing the redirect to /profile then setting the token (step #7 then #6), so everytime the user is using google oauth2 login, its always gonna be redirected back to / since by the time it arrives at /profile, it doesnt have the token
here's the code on the above's flow
#./server.js
const express = require('express')
const next = require('next')
const Passport = require('./server/middleware/passport')
const Api = require('./server/api')
const port = parseInt(process.env.PORT, 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app
.prepare()
.then(() => {
const server = express()
// ... other unrelated things
server.use(Passport.initialize())
Api.passport.facebook(server)
Api.passport.facebookCallback(server)
Api.passport.google(server)
Api.passport.googleCallback(server)
// ... other unrelated things
server.all('*', (req, res) => handle(req, res))
server.listen(port, (error) => {
if (error) throw error
// ... other unrelated things
})
})
#./server/api.js
const Passport = require('middleware/passport')
function setCookie(req, res, next) {
res.cookie('token', req.user.auth.token, {
httpOnly: true,
sameSite: 'strict',
path: '/',
secure: process.env.NODE_ENV !== 'development',
})
next()
}
function facebook(app) {
return app.get('/auth/facebook', (req, res, next) => {
Passport.authenticate('facebook', {
scope: ['email', 'public_profile']
})(req, res, next)
})
}
function facebookCallback(app) {
return app.get(
'/auth/callback/facebook',
Passport.authenticate('facebook', { session: false, failureRedirect: '/' }),
setCookie,
(req, res) => {
res.redirect('/profile')
},
)
}
function google(app) {
return app.get('/auth/google', (req, res, next) => {
Passport.authenticate('google', {
scope: [
'https://www.googleapis.com/auth/userinfo.email ',
'https://www.googleapis.com/auth/userinfo.profile ',
],
prompt: 'consent',
authType: 'rerequest',
accessType: 'offline',
})(req, res, next)
})
}
function googleCallback(app) {
return app.get(
'/auth/callback/google',
Passport.authenticate('google', { failureRedirect: '/', session: false }),
setCookie,
(req, res) => {
res.redirect('/profile')
},
)
}
module.exports = {
passport: {
facebook,
facebookCallback,
google,
googleCallback,
}
}
#./server/middleware/passport.js
const axios = require('axios')
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20').Strategy
const FacebookStrategy = require('passport-facebook').Strategy
passport.serializeUser((user, done) => {
done(null, user)
})
passport.deserializeUser((obj, done) => {
done(null, obj)
})
function verifyCallback(req, ... , done) {
process.nextTick(async () => {
try {
const options = {
baseURL: baseUrl, // My remote api url
method: 'POST',
url: '/auth/signin',
headers: {
'Content-Type': 'application/json',
},
data: JSON.stringify({
// email, fullname, etc
}),
}
const response = await axios(options)
return done(null, response.data)
} catch (error) {
const { response } = error
return done(JSON.stringify(response.data, null, 2), null)
}
})
}
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: callbackURLGoogle,
passReqToCallback: true,
}, verifyCallback))
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
callbackURL: callbackURLFacebook,
enableProof: true,
profileFields: ['id', 'name', 'email', 'picture.type(large)'],
passReqToCallback: true,
}, verifyCallback))
module.exports = passport
I console.log() things, just to figure out if it falls to the correct sequence of flow, the console doesn't seem to log anything suspicious, is there's something i am missing here?
PS: i am also using next js with custom server
I was facing the same problem and was able to send cookies by using custom callback.
router.get('/google/callback', (req, res) => {
passport.authenticate('google', {session: false, failureRedirect:'/auth/google/failure'},
async(err, user) => {
// You can send cookies and data in response here.
})(req, res)
})
Please refer custom callback section in documentation for explanation.

Passport Slack OAuth Scope Throwing Error with Identity

I'm trying to request a slack oauth token using the passport-slack module with the following code.
passport.use(new SlackStrategy({
clientID: xxxxxxx,
clientSecret: xxxxxxx,
scope: [
'chat:write:user',
'channels:read'
],
callbackURL: xxxxxxx+"/slack/callback"
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}
));
router.use(passport.initialize());
router.use(require('body-parser').urlencoded({ extended: true }));
router.get('/authorize', function(req, res, next) {
passport.authenticate('Slack', function(err, user, info) {
next();
})(req, res, next);
});
/* OAuth Callback flow */
router.get('/callback', passport.authorize('Slack', { failureRedirect: '/authorize' }), function(req, res) {
});
However, I am getting the following response "missing_scope","needed":"identity.basic","provided":"identify,channels:read,chat:write:user"}.
But when I add identify.basic as a scope it errors saying I can't request both the identity and other scopes at the same time. Anyone know how to resolve this? I just want to generate an oauth token so my api can post to slack as a user.

How can I authenticate a GraphQL endpoint with Passport?

I have a GraphQL endpoint:
app.use('/graphql', graphqlHTTP(request => ({
graphiql: true,
schema
})));
I also have a Passport route for logging in (and handling the callback, since I'm using Google OAuth2):
this.app.get('/login', passport.authenticate('google'));
this.app.get('/auth/callback/google', ....
Passport add a user to the request, and all of the articles I can find online recommend authenticating in each of my GraphQL resolvers using that:
resolve: (root, args, { user }) => {
if (!user) throw new NotLoggedInError();
However it doesn't make sense to have to add that logic to every resolver when it applies to all of them, so I was hoping to somehow authenticate the entire endpoint.
The problem is that I'm not sure how to combine middleware. I tried the following but it just broke the endpoint:
app.use('/graphql', passport.authenticate('google'), graphqlHTTP(request => ({
graphiql: true,
schema
})));
I have the following working. Some issues I had were around making sure my google API was enabled and the proper scopes were enabled. I am also only using the passport middleware on the auth endpoints and using an isAuthenticated middleware to check if the session is authenticated and if not redirect to the auth endpoint. also putting the request object into the context so that it can be used by the resolver to potentially authorize the user. You would of course need to update the user lookup as I am just passing mock data.
import express from "express";
import graphqlHTTP from "express-graphql";
import passport from "passport";
import cookieParser from "cookie-parser";
import session from "express-session";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { buildSchema } from "graphql";
const PORT = 5000;
const data = [
{ id: "1", name: "foo1" },
{ id: "2", name: "foo2" },
{ id: "3", name: "foo3" },
];
const def = `
type Foo {
id: String!
name: String
}
type Query {
readFoo(id: String!): Foo
}
schema {
query: Query
}
`;
const schema = buildSchema(def);
const fieldMap = schema.getType("Query").getFields();
fieldMap.readFoo.resolve = (source, args) => {
return data.filter(({ id }) => id === args.id)[0] || null;
};
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((obj, done) => {
done(null, obj);
});
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `http://localhost:${PORT}/auth/google/callback`,
},
(accessToken, refreshToken, profile, cb) => {
return cb(null, {
id: "1",
username: "foo#bar.baz",
googleId: profile.id,
});
}
)
);
function isAuthenticated(req, res, next) {
return req.isAuthenticated() ? next() : res.redirect("/auth/google");
}
const app = express();
app.use(cookieParser());
app.use(
session({
secret: "sauce",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
app.get("/auth/fail", (req, res) => {
res.json({ loginFailed: true });
});
app.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile"] })
);
app.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/auth/fail" }),
(req, res) => {
res.redirect("/graphql");
}
);
app.use(
"/graphql",
isAuthenticated,
graphqlHTTP((req) => ({
schema,
graphiql: true,
context: req,
}))
);
app.listen(PORT, () => {
console.log("Started local graphql server on port ", PORT);
});
vbranden's answer was excellent, and it is the basis of this answer. However, his answer has a lot of other code which obfuscates the solution a bit. I didn't want to mess with it, since it offers a more complete view of things, but hopefully this answer will be helpful in its own way by being more direct. But again, all credit for this solution belongs to vbranden (please upvote his answer accordingly).
If you make an isAuthenticated function with the appropriate signature (request, response, next) you can then "chain" that function in when you setup your GraphQL endpoint:
function isAuthenticated(req, res, next) {
return req.isAuthenticated() ?
next() :
res.redirect('/auth/google');
}
app.use(
'/graphql',
isAuthenticated,
graphqlHTTP(req => ({
schema,
graphiql: true,
context: req
}))
);