Open cSarcasme opened 1 year ago
Anyone to help me please ?
I ty by advance.
Hi, I will look at it today and give you some answers.
Jan
thanks a lot mr janhalama i will wait
From the call stack you provided it looks like that Twitter strategy is not configured properly. Specifically custom headers are not configured.
Custom headers are configured if the clientType
option is set to confidential
in the strategy constructor. From the code snippets you provided I can not say where exactly is the problem.
What is the version of @superfaceai/passport-twitter-oauth2 lib you have installed? Did you try the basic example app with your Twitter App configuration?
ty for your quick answer i will try with the basic example and come back to you with the result and maybe it will help me find the solution.
Also i have that intalled "@superfaceai/passport-twitter-oauth2": "^1.2.3",
i think it i the last version ?
you also writte lib behind @superfaceai/passport-twitter-oauth2 may be you speak about other thing.
i will search also more information about custom header
i come back when i have all one that ty
ty for your quick answer i will try with the basic example and come back to you with the result and maybe it will help me find the solution.
Or, if you can provide a minimum reproducible example, I can help.
Also i have that intalled "@superfaceai/passport-twitter-oauth2": "^1.2.3",> i think it i the last version ?
Yes it is the latest version, there was an update in the library recently, but as you are on the latest version it is not the case.
ia am on it now i had to finish a other part of my code before to come back to that so i am on it i look little more on my side and if i dont find i will provide you a minimum reproductible sorry for the delay.
Now i don' t do other dev thing until i find the solution ;) haha ty for your time
So i have try many things and if i put in the developer portal on native app public client it work(but i am not sure it is good to do that for security)
I have look inside the module and normally if it is in clientType: 'confidential' the module take the clientID and clientSecret and create himself the header i don t understand why it not work.
I have try myself to add the headers like that too and always the same message
passport.use(
// <2> Strategy initialization
new Strategy(
{
clientID: process.env.TWITTER_CLIENT_ID,
clientSecret: process.env.TWITTER_CLIENT_ID_SECRET,
clientType: 'confidential',
callbackURL: process.env.CALLBACK_TWITTER,
customHeaders: {
Authorization:
'Basic ' +
Buffer.from(
`${process.env.TWITTER_CLIENT_ID}:${process.env.TWITTER_CLIENT_ID_SECRET}`
).toString('base64'),
},
},
// <3> Verify callback
async (accessToken, refreshToken, profile, done) => {
console.log('AccessToken:', accessToken);
console.log('RefreshToken:', refreshToken);
//console.log('profile', profile);
try {
const { id, _json } = profile;
const name = _json.name;
const userName = _json.username;
const findUser = await TwitterUser.findOne({ twitterId: id });
if (findUser) {
console.log('access token', accessToken);
console.log('tokenSecret', refreshToken);
findUser.accessToken = jwt.sign(
{ accessToken },
process.env.JWT_TWITTER_CLIENT_TOKEN
);
findUser.refreshToken = jwt.sign(
{ refreshToken },
process.env.JWT_TWITTER_CLIENT_TOKEN_SECRET
);
await findUser.save();
return done(null, findUser);
} else {
console.log('access token', accessToken);
console.log('tokenSecret', refreshToken);
//console.log('pofileTest',id,name, screen_name,followers_count)
const newUser = await TwitterUser.create({
twitterId: id,
name: name,
username: userName,
accesstoken: jwt.sign(
{ accessToken },
process.env.JWT_TWITTER_CLIENT_TOKEN
),
refreshtoken: jwt.sign(
{ refreshToken },
process.env.JWT_TWITTER_CLIENT_TOKEN_SECRET
),
});
return done(null, newUser);
}
} catch (error) {
console.error(error);
return done(error, null);
}
}
)
);
I have try to change customHeaders to headers and same issue.
I need to advance on my project (i have the automation quest to do link to twitter) so i will put on public in wait to find a solution but i really need to find it for the security before go to live.
if you see something i have no see tell me ty for your help
@cSarcasme I had this issue and the problem was actually my Nginx proxy not forwarding the protocol and I needed to add
proxy_set_header X-Forwarded-Proto $scheme;
to my Nginx configuration
Another possibility is that your cookie is not being set correctly probably becuase of domain or some other settings.
Also if you are behind a proxy you need to trust proxy in express.
I am experiencing this same problem and I am not making use of Nginx or any other proxy server, just using express directly.
Once twitter completes the authorization and redirects, it reports the following error:
TokenError: Missing valid authorization header
at OAuth2Strategy.parseErrorResponse (E:\Programming-School\vesper-server\node_modules\passport-oauth2\lib\strategy.js:373:12)
at OAuth2Strategy._createOAuthError (E:\Programming-School\vesper-server\node_modules\passport-oauth2\lib\strategy.js:420:16)
at E:\Programming-School\vesper-server\node_modules\passport-oauth2\lib\strategy.js:177:45
at E:\Programming-School\vesper-server\node_modules\oauth\lib\oauth2.js:196:18
at passBackControl (E:\Programming-School\vesper-server\node_modules\oauth\lib\oauth2.js:132:9)
at IncomingMessage.<anonymous> (E:\Programming-School\vesper-server\node_modules\oauth\lib\oauth2.js:157:7)
at IncomingMessage.emit (node:events:531:35)
at endReadableNT (node:internal/streams/readable:1696:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
I'm also using the google and Facebook authorization strategies with passport and those are working okay. Only this twitter OAuth2 one fails. I've tried to compress all of this into a minimal example but naturally, it's not very minimal (there's a lot of pieces to get this working). In any case, I'll attach it here. Any help or suggestions would be much appreciated!
// Core libraries
import fs from 'fs'
import https from 'https'
// Debug output libraries
import Debug from 'debug'
import dotenv from 'dotenv'
// Express and middleware libraries
import express from 'express'
import morganLog from 'morgan'
import cookieParser from 'cookie-parser'
import querystring from 'querystring'
// MongoDB session management
import session from 'express-session'
import connectMongoDB from 'connect-mongodb-session'
// MongoDB driver for user accounts
import { MongoClient, ServerApiVersion } from 'mongodb'
// Passport and various strategies
import passport from 'passport'
import { Strategy as GoogleStrategy } from 'passport-google-oidc'
import { Strategy as FacebookStrategy } from 'passport-facebook'
import { Strategy as XStrategy } from '@superfaceai/passport-twitter-oauth2'
// Read secrets and settings from .env file to local variables
dotenv.config()
const MONGO_SERVER = process.env.MONGO_SERVER ?? 'badServer.mongodb.net'
const MONGO_USER = process.env.MONGO_USER ?? 'badUser'
const MONGO_PASS = process.env.MONGO_PASS ?? 'badPass'
const SESSION_SECRET = process.env.SESSION_SECRET ?? 'keyboard cat'
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID ?? 'badGoogleId'
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET ?? 'badGoogleSecret'
const FACEBOOK_CLIENT_ID = process.env.FACEBOOK_CLIENT_ID ?? 'badFacebookId'
const FACEBOOK_CLIENT_SECRET = process.env.FACEBOOK_CLIENT_SECRET ?? 'badFacebookSecret'
const X_CLIENT_ID = process.env.X_CLIENT_ID ?? process.env.TWITTER_CLIENT_ID ?? 'badXKey'
const X_CLIENT_SECRET = process.env.X_CLIENT_SECRET ?? process.env.TWITTER_CLIENT_SECRET ?? 'badXSecret'
const LISTEN_PORT = process.env.DEV_PORT
// Create debug logger
const debug = Debug('vesper:server-minimal')
// Raw login page
const loginPage = fs.readFileSync('public/login.html', { encoding: 'utf8' })
// Build connection URI
const mongoDbURI = `mongodb+srv://${MONGO_USER}:${MONGO_PASS}@${MONGO_SERVER}/?retryWrites=true&w=majority`
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
const client = new MongoClient(mongoDbURI, {
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true
}
})
// Lookup credentials for a given provider and providerId (might be null)
async function retrieveCredentials (provider, providerId) {
try {
const collection = client.db('Users').collection('FederatedCredentials')
return await collection.findOne({ provider, providerId })
} catch (error) {
debug(error)
return null
}
}
// Lookup a user by their userId
async function retrieveUser (userId) {
try {
const collection = client.db('Users').collection('Users')
return await collection.findOne({ _id: userId })
} catch (error) {
debug(error)
return null
}
}
// Create a new user entry in the Users collection
async function createUser (username) {
try {
const collection = client.db('Users').collection('Users')
const result = await collection.insertOne({ username })
return result.acknowledged ? result.insertedId : null
} catch (error) {
debug(error)
return null
}
}
// Create a new federated credential entry in the FederatedCredentials collection
async function createFederatedCredential (userId, provider, providerId) {
try {
const collection = client.db('Users').collection('FederatedCredentials')
const result = await collection.insertOne({ userId, provider, providerId })
return result.acknowledged ? result.insertedId : null
} catch (error) {
debug(error)
return null
}
}
// Just-In-Time provisioning of user credentials
async function jitProvision (provider, profile) {
try {
const credentials = await retrieveCredentials(provider, profile.id)
if (!credentials) {
const newUserId = await createUser(profile.displayName)
await createFederatedCredential(newUserId, provider, profile.id)
return { id: newUserId, name: profile.displayName }
} else {
const user = await retrieveUser(credentials.userId)
return user
}
} catch (error) {
debug(error)
return null
}
}
// Configure Passport Strategies
// Authorize via Google Credentials
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: '/oauth2/redirect/google',
scope: ['profile', 'email'],
state: true
}, async (issuer, profile, cb) => {
debug('Google profile:')
debug(profile)
const user = await jitProvision(issuer, profile)
if (!user) { return cb(new Error('Failed to provision credentials')) }
const cred = {
providerId: profile?.emails?.[0]?.value ?? profile.id,
provider: issuer,
name: profile.displayName ?? undefined
}
return cb(null, user, { credential: cred })
}))
// Authorize via Facebook Credentials
passport.use(new FacebookStrategy({
clientID: FACEBOOK_CLIENT_ID,
clientSecret: FACEBOOK_CLIENT_SECRET,
callbackURL: '/oauth2/redirect/facebook',
scope: ['public_profile', 'email'],
state: true
}, async (accessToken, refreshToken, profile, cb) => {
debug('Meta profile:')
debug(profile)
const user = await jitProvision('https://www.facebook.com', profile)
if (!user) { return cb(new Error('Failed to provision credentials')) }
const cred = {
providerId: profile?.emails?.[0]?.value ?? profile.id,
provider: 'https://www.facebook.com',
name: profile.displayName ?? undefined
}
return cb(null, user, { credential: cred })
}))
// Authorize via Twitter/X Credentials
passport.use(new XStrategy({
clientID: X_CLIENT_ID,
clientSecret: X_CLIENT_SECRET,
clientType: 'confidential',
callbackURL: '/oauth/callback/x',
scope: ['tweet.read', 'users.read', 'offline.access'],
state: true
}, async (accessToken, refreshToken, profile, cb) => {
debug('Twitter profile:')
debug(profile)
const user = await jitProvision('https://x.com', profile)
if (!user) { return cb(new Error('Failed to provision credentials')) }
const cred = {
providerId: profile?.emails?.[0]?.value ?? profile.username ?? profile.id,
provider: 'https://x.com',
name: profile.displayName ?? undefined
}
return cb(null, user, { credential: cred })
}))
// Other passport configuration
passport.serializeUser((user, cb) => {
process.nextTick(() => {
cb(null, { id: user.id, username: user.username, name: user.name })
})
})
passport.deserializeUser((user, cb) => {
process.nextTick(() => {
return cb(null, user)
})
})
function setFederatedCredentialCookie (req, res, next) {
const credential = req.authInfo.credential
if (!credential) { return next() }
res.cookie('fc', querystring.stringify(credential))
next()
}
// Initialize express server middleware
const app = express()
app.use(morganLog('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static('public'))
// Set up session middleware
const MongoDBStore = connectMongoDB(session)
app.use(session({
secret: SESSION_SECRET,
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
store: new MongoDBStore({ uri: mongoDbURI, databaseName: 'Users', collection: 'Sessions' })
}))
// Install passport authentication
app.use(passport.authenticate('session'))
// Show the login page
app.get('/login', (req, res) => {
res.setHeader('Content-Type', 'text/html')
res.send(loginPage)
})
// Google login routes
app.get('/login/federated/google', passport.authenticate('google'))
app.get('/oauth2/redirect/google', passport.authenticate('google', {
keepSessionInfo: true,
failureRedirect: '/login'
}), setFederatedCredentialCookie, (req, res) => {
debug('Google auth success')
res.json(req.user)
})
// Facebook login routes
app.get('/login/federated/facebook', passport.authenticate('facebook'))
app.get('/oauth2/redirect/facebook', passport.authenticate('facebook', {
keepSessionInfo: true,
failureRedirect: '/login'
}), setFederatedCredentialCookie, (req, res) => {
debug('Meta/Facebook auth success')
res.json(req.user)
})
// Twitter login routes
app.get('/login/federated/x', passport.authenticate('twitter'))
app.get('/oauth/callback/x', passport.authenticate('twitter', {
keepSessionInfo: true,
failureRedirect: '/login'
}), setFederatedCredentialCookie, (req, res) => {
debug('twitter/x auth success')
res.json(req.user)
})
// Logout route
app.post('/logout', (req, res, next) => {
req.logout((err) => {
if (err) { return next(err) }
debug('Logged out')
res.json({ message: 'Logged out' })
})
})
// Set express port
app.set('port', LISTEN_PORT)
// Create HTTPS server.
const SSLKey = fs.readFileSync('server/testingCert/key.pem')
const SSLCert = fs.readFileSync('server/testingCert/cert.pem')
const server = https.createServer({ key: SSLKey, cert: SSLCert }, app)
// Set logging callbacks
server.on('listening', onListening)
// Listen on provided port, on all network interfaces.
server.listen(LISTEN_PORT)
// Event listener for HTTP server "listening" event.
function onListening () {
const addr = server.address()
debug('Listening on https://localhost:' + addr.port)
}
@Olliebrown I have not found any problems with the code you provided.
I can simulate the same error response by configuring the X application as confidential, and then setting the public
client type option in my test client code. This is expected, because in the case of the public client type, the `Authorization' header is not sent.
It is the response on the token endpoint that returns the error. The initial code exchange at the authorization endpoint succeeds.
RFC 6749 defines two different methods of client authentication, the first is the authorization header using the basic auth scheme and the second using body params. The node-oauth lib (used under the hood) implements just body params auth and Twitter accepts just auth header. Google and Facebook OAuth 2.0 implementations accept the body params auth, so you have no problems with them.
Our sample app works fine for me, have you tried running it on your dev box?
@janhalama Thank you for taking the time to look through the code! I appreciate it.
I have not tried the simple app so I will give that a go. It is perhaps an issue with my specific box or network environment. I appreciate the lead to follow. I'll report back if I find anything to explain what is happening for me.
I have search a lot a solution for my issue but i dont find it!
you are my last hope :) !
error i have when i connect with twitter i arrive to the twitter page and give the authorization on my twitter account after that i have this error:
TokenError: Missing valid authorization header at OAuth2Strategy.parseErrorResponse (C:\Users\haatman\Desktop\dbq2formydev-main\dbq2formydev-main\bck\node_modules\passport-oauth2\lib\strategy.js:373:12) at OAuth2Strategy._createOAuthError (C:\Users\haatman\Desktop\dbq2formydev-main\dbq2formydev-main\bck\node_modules\passport-oauth2\lib\strategy.js:420:16) at C:\Users\haatman\Desktop\dbq2formydev-main\dbq2formydev-main\bck\node_modules\passport-oauth2\lib\strategy.js:177:45 at C:\Users\haatman\Desktop\dbq2formydev-main\dbq2formydev-main\bck\node_modules\oauth\lib\oauth2.js:191:18 at passBackControl (C:\Users\haatman\Desktop\dbq2formydev-main\dbq2formydev-main\bck\node_modules\oauth\lib\oauth2.js:132:9) at IncomingMessage. (C:\Users\haatman\Desktop\dbq2formydev-main\dbq2formydev-main\bck\node_modules\oauth\lib\oauth2.js:157:7)
at IncomingMessage.emit (node:events:526:35)
at endReadableNT (node:internal/streams/readable:1359:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
my roads:
express-session and save cookie into mongoose
I hope someone can help me i ty by advance :)
If you need other information tell me.