folks. I'm working on a tiny app using OAuth (via Battle.Net) and PassportJS + Express, and I keep getting an Internal Server Error no matter what I do.
I've read their code numerous times, tried using ES6 syntax and ES5 syntax (I'm using babel-watch for transpiling), to no avail.
This is my current Express setup using the BNet Passport Strategy:
import fs from 'fs';
import path from 'path';
import express from 'express';
import cookieParser from 'cookie-parser';
import { ApolloServer } from 'apollo-server-express';
import session from 'express-session';
import passport from 'passport';
const BnetStrategy = require('passport-bnet').Strategy;
// Get our stuff from env vars
const BNET_ID = process.env.BNET_ID;
const BNET_SECRET = process.env.BNET_SECRET;
// Our GQL stuff
import resolvers from './gql/resolvers';
import typeDefs from './gql/typeDefs';
// Express Stuff
const app = express();
// Integrate Apollo middleware
const gqlServer = new ApolloServer({
typeDefs,
resolvers,
});
// Passport User serialize/deserialize
passport.serializeUser((user, done) => {
return done(null, user)
});
passport.deserializeUser((obj, done) => {
return done(null, obj)
});
// Passport middleware w/ bnet?
passport.use(new BnetStrategy({
clientID: BNET_ID,
clientSecret: BNET_SECRET,
scope: ['wow.profile'],
region: 'us',
passReqToCallback: true,
callbackURL: "http://localhost:4000/auth/bnet/callback"
},
(req, accessToken, refreshToken, profile, done) => {
console.log(req.user, accessToken, profile);
return done(null, profile)
})
);
app.get('/auth/bnet', passport.authenticate('bnet'));
app.get('/auth/bnet/callback', passport.authenticate('bnet', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/');
}
)
app.get('/', function(req, res) {
if(req.isAuthenticated()) {
var output = '<h1>Express OAuth Test</h1>' + req.user.id + '<br>';
if(req.user.battletag) {
output += req.user.battletag + '<br>';
}
output += 'Logout';
res.send(output);
} else {
res.send('<h1>Express OAuth Test</h1>' +
'Login with Bnet');
}
});
// Listen!
app.listen({ port: 4000 }, () => {
console.log(`GQL server is listening at http://localhost:4000${gqlServer.graphqlPath}`);
});
When I flush the cookies from my browser and visit the server's index page, it lets me login using my BNet account, and even triggers the Mobile Authenticator on my phone (which I accepted), but then after I accept the auth on my phone, it tosses this error:
AuthorizationError: Internal server error
at Strategy.OAuth2Strategy.authenticate (/home/czbaker/Projects/wow-warmode/server/node_modules/passport-oauth2/lib/strategy.js:131:25)
at attempt (/home/czbaker/Projects/wow-warmode/server/node_modules/passport/lib/middleware/authenticate.js:361:16)
at authenticate (/home/czbaker/Projects/wow-warmode/server/node_modules/passport/lib/middleware/authenticate.js:362:7)
at Layer.handle [as handle_request] (/home/czbaker/Projects/wow-warmode/server/node_modules/express/lib/router/layer.js:95:5)
at next (/home/czbaker/Projects/wow-warmode/server/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/home/czbaker/Projects/wow-warmode/server/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/home/czbaker/Projects/wow-warmode/server/node_modules/express/lib/router/layer.js:95:5)
at /home/czbaker/Projects/wow-warmode/server/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/home/czbaker/Projects/wow-warmode/server/node_modules/express/lib/router/index.js:335:12)
at next (/home/czbaker/Projects/wow-warmode/server/node_modules/express/lib/router/index.js:275:10)
I've checked callbacks for all of the functions to see that they're formatted correctly (returning done(null, <value>) seems to be a big thing people miss, after looking around at similar issues, but it's fine here.
I have no idea what I'm doing wrong, so any assistance would be super appreciated. Thanks!
It turns out that my problem was that the callback URL needed to use HTTPS, as is a requirement from the OAuth2 API on Blizzard's end, and I failed to realize that.
Upon setting up SSL/HTTPS on my Express server, everything worked fine, so I guess the JS error itself wasn't super helpful.
Related
I thought I followed all of the docs correctly to implement this, however, I am getting a TokenError: Bad Request that has the error message invalid_grant.
My server is super simple:
require('dotenv').config();
import createServer from './createServer';
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const server = createServer();
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:4000/auth/callback',
},
(accessToken, refreshToken, profile, cb) => {
console.log(accessToken);
console.log(refreshToken);
console.log(profile);
cb(null, profile);
},
),
);
server.express.use(
'/auth',
passport.authenticate('google', {
scope: ['email', 'profile'],
session: false,
}),
);
server.express.use(
'/auth/callback',
passport.authenticate('google', {
successRedirect: 'http://localhost:3000/authenticate',
failureRedirect: 'http://localhost:3000/authenticate',
}),
);
server.start(
{
cors: {
credentials: true,
origin: 'http://localhost:3000',
},
},
() => console.log('Server is running on http://localhost:4000'),
);
Is this a problem with the way that I have setup Google in the cloud platform? I can't figure out where I went wrong. My callback is correctly setup. I'm not sure where else to look for a mistake?
Another thing that is confusing is that the GoogleStategy is console logging the user profile and the access token that is returned. I am guessing that the error comes when the callback route tries to verify the code from the URL. Can anyone point me in a direction to look to better troubleshoot this? Thanks in advance.
I found a solution that works for me, but I am still unclear if it is "best-practice" or not. I would love someone with more GraphQL experience to chime in.
I followed the directions in the docs to authenticate in the front-end: https://developers.google.com/identity/sign-in/web/backend-auth
Then I wrote a query called isAuthenticated on the back-end that I can use to verify the token.
async isAuthenticated(_: any, { token }) {
if(!token) {
return null;
}
const ticket = await client.verifyIdToken({
idToken: token,
audience: process.env.GOOGLE_CLIENT_ID,
});
return payload;
},
I use a React component to check the token in localStorage before rendering any protected routes. This is working for me.
I'm doing administration dashboard with use a token jwt. I would like to the user can obtain access to the middleware only if logged. I want to do that if he is logged, the server is shows home page if not, then he is redirect to login page. Below is my middleware index.js
const auth = require('feathers-authentication');
const pupils = require('./pupils');
const login = require('./login');
const home = require('./home');
module.exports = function () {
// Add your custom middleware here. Remember, that
// in Express the order matters
const app = this; // eslint-disable-line no-unused-vars
app.use('/pupils.html', pupils());
app.use('/login.html', login());
app.use('/', home());
app.post('/login', auth.express.authenticate('local', { successRedirect: '/', failureRedirect: '/login.html' }));
};
and pupils.js with use a handlebars
const lang = require('../../config/pupils.json');
module.exports = function (options = {}) {
return function login(req, res) {
res.render('home', lang);
};
};
If I open the main path '/' then it is redirecting to /login.html
I try authentication my user below code:
app.authenticate({
strategy: 'local',
email: 'username',
password: 'password'
}).then(function(result){
console.log('Authenticated!', result);
}).catch(function(error){
console.error('Error authenticating!', error);
});
When use app.authenticate method I receive a token object and it is storage in local storege but when I try again open main path, again I redirect to /login.html. What I do wrong?
How can I to do in this way:
1) I open this address http://127.0.0.1:3030:/
2) middleware on the server check if user is logged
3) if logged show home page, if not show login page
how to implement the social login such as facebook and google authentication
using Json Web token (jwt) ,same as Auth0 has implemented. i am using node js as back end and angular 2 as front end.
Perhaps I'm confused by your phrasing but facebook, google, and JWT are distinct, individual authentication strategies.
But check out passport.js it offers hundreds of authentication strategies:
defined strategy: (this example uses mongoose)
const jwtOptions = {
jwtFromRequest: PJWT.ExtractJwt.fromAuthHeader(),
secretOrKey: SECRET, // externally defined private encryption key
}
export const jwtStrategy = new PJWT.Strategy(jwtOptions,
function (jwt_payload, done) {
MongoUser.findOne({ email: jwt_payload.email }, function (err, user) { // presumes the user's email is in the jwt
if (err) return done(err, false);
if (user) return done(null, user);
else return done(null, false);
});
});
also these are needed by passport:
type Done = (err: any, user?: {}) => void;
export const serializeMongoUser = function (user: UserDoc, done: Done) {
done(null, user._id);
}
export const deserializeMongoUser = function (_id: any, done: Done) {
MongoUser.findById(_id, function (err, user) {
done(err, user);
});
}
add the middleware to your server:
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(ppt.serializeMongoUser);
passport.deserializeUser(ppt.deserializeMongoUser);
passport.use(ppt.jwtStrategy);
authenticate from a service in your app:
authenticate(): Observable<any> {
if (!this.security.jwt) return Observable.of({sucess: false})
return this.http.get('/api/authenticate', authHeaders(this.security))
.map(res => res.json());
}
call this function when you need to authenticate:
*note the authHeaders(this.security)
this.security is a subscription to the JWT token in my ngrx store, but you could also put it in localStorage.
export function authHeaders(security: Security) {
const headers = new Headers();
headers.append('Content-Type', 'application/json; charset=utf-8');
headers.append('Authorization', `JWT ${security.jwt}`);
return { headers };
}
calling authenticate() will return {success: true} or {success: false}
Here is a little example I created to show the basics of socket.io security with JWT authentication. The backend is nodeJS, socket.io, and express.
In the long run you should use passport for user sessions but the below is good to see so you get the whole picture.
Overview
When browsing to http://127.0.0.1:3001, the express middleware socketAuthenticated will create a jwt based on the hard coated user credentials (should use passport sessions for real user info).
This token is then passed to the server EJS view and used to establish a socket connection.
Create a new folder for the app and run npm install express socketio-jwt jsonwebtoken ejs-locals socket.io
Create a "public" folder within the root folder and put the socket.io-client JS file inside (you can run bower install socket.io-client to get the js file)
Create app.js file within the root folder with the following:
let express = require('express'),
socketioJwt = require('socketio-jwt'),
jwt = require('jsonwebtoken'),
port = 3001
engine = require('ejs-locals');
let app = express();
let socketAuthenticated = (req, res, next) => {
let token = jwt.sign({firstName:"jason", lastName:"owl"}, "secret", { expiresIn: 60*5 }); // assuming you have some user object in req
res.token = token; // front end will use this token to authenticate its socket connection
next();
};
app.engine('ejs', engine);
app.set('view engine', 'ejs');
app.set('view options', {defaultLayout: 'layout'});
app.use(express.static('public'));
app.use(socketAuthenticated);
app.route('/')
.get((req, res) => {
console.log(`Server Passing Token: ${res.token} to client`);
res.render('index', { jwt: res.token });
});
let server = require('http').Server(app);
let io = require('socket.io')(server);
// Setup socket server to use JWT
io.set('authorization', socketioJwt.authorize({
secret: "secret",
handshake: true
}));
io.of('/default').on('connection', (socket) => {
socket.on('data', (newConfig) => {
console.log('Data Event Recieved!');
});
});
server.listen(port);
console.log(`\Application: ${process.pid} Listening On Port: ${port}`);
4) Create a new folder called views and create a file called index.ejs:
<script type="text/javascript" src="/socket.io.js"></script>
<script>
var jwttoken = <%- JSON.stringify(jwt) %>;
function connect_socket (token) {
var socket = io.connect('/default', {
query: 'token=' + token
});
return socket;
}
var socket = connect_socket(jwttoken);
socket.emit('data');
</script>
run node app.js and open the browser to localhost:3001.
I have been able to use node.js and passport.js to connect to Facebook using the GitHub project available at: https://github.com/jaredhanson/passport-facebook/tree/master/examples/login.
Here is the what the app.js code is doing:
var express = require('express')
, passport = require('passport')
, util = require('util')
, FacebookStrategy = require('passport-facebook').Strategy
, logger = require('morgan')
, session = require('express-session')
, bodyParser = require("body-parser")
, cookieParser = require("cookie-parser")
, methodOverride = require('method-override');
var FACEBOOK_APP_ID = "--insert-facebook-app-id-here--"
var FACEBOOK_APP_SECRET = "--insert-facebook-app-secret-here--";
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing. However, since this example does not
// have a database of user records, the complete Facebook profile is serialized
// and deserialized.
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
// Use the FacebookStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Facebook
// profile), and invoke a callback with a user object.
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's Facebook profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Facebook account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}
));
var app = express();
// configure Express
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(logger());
app.use(cookieParser());
app.use(bodyParser());
app.use(methodOverride());
app.use(session({ secret: 'keyboard cat' }));
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res){
res.render('index', { user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
res.render('account', { user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { user: req.user });
});
// GET /auth/facebook
// Use passport.authenticate() as route middleware to authenticate the
// request. The first step in Facebook authentication will involve
// redirecting the user to facebook.com. After authorization, Facebook will
// redirect the user back to this application at /auth/facebook/callback
app.get('/auth/facebook',
passport.authenticate('facebook'),
function(req, res){
// The request will be redirected to Facebook for authentication, so this
// function will not be called.
});
// GET /auth/facebook/callback
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
app.listen(3000);
// Simple route middleware to ensure user is authenticated.
// Use this route middleware on any resource that needs to be protected. If
// the request is authenticated (typically via a persistent login session),
// the request will proceed. Otherwise, the user will be redirected to the
// login page.
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
The code works great if I am just using the internet with no proxy server but if I am behind a corporate firewall then I get the following error:
InternalOAuthError: Failed to obtain access token
at Strategy.OAuth2Strategy._createOAuthError (C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\lib\strategy.js:348:17)
at C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\lib\strategy.js:171:43
at C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:177:18
at ClientRequest.<anonymous> (C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:148:5)
at emitOne (events.js:77:13)
at ClientRequest.emit (events.js:169:7)
at TLSSocket.socketErrorListener (_http_client.js:259:9)
at emitOne (events.js:77:13)
at TLSSocket.emit (events.js:169:7)
at emitErrorNT (net.js:1253:8)
Does anyone know how to setup the code above to go through a corporate proxy server for connectivity? I have tried setting the npm configuration properties of proxy, http-proxy and https-proxy but it does not appear to make a difference when I run this application. Any help you can offer would be greatly appreciated. Thank you.
Adding this code in /node_moduels/oauth/lib/oauth.js would fix the issue temporarily.
var HttpsProxyAgent = require('https-proxy-agent');
if (process.env['https_proxy']) {
httpsProxyAgent = new HttpsProxyAgent(process.env['https_proxy']);
}
Finally, set the httpsProxyAgent to the request options right before _executeRequest gets called like this:
options.agent = httpsProxyAgent
oauth2.js does not take in consideration the proxy environment variables when it performed the requests. Blocking the web app to get authenticated (receive the access token and the github user info)
So I tried with this workarround that let know to the oauth2.js that uses the proxy to communicate.
You usually will find the oauth2.js code that needs to adapt at: your_project/node_modules/oauth/lib/oauth2.js
var querystring= require('querystring'),
crypto= require('crypto'),
https= require('https'),
http= require('http'),
URL= require('url'),
OAuthUtils= require('./_utils');
// line codes to add
var HttpsProxyAgent = require('https-proxy-agent');
let httpsProxyAgent = null
if (process.env['https_proxy']) {
httpsProxyAgent = new HttpsProxyAgent("http://127.0.0.1:1087");
// fill in your proxy agent ip and port
}
....
// line codes to add
options.agent = httpsProxyAgent;
this._executeRequest( http_library, options, post_body, callback );
}
exports.OAuth2.prototype._request= function(method, url, headers, post_body, access_token, callback) {
...
Adding
options.agent = httpsProxyAgent
worked for me. But I added it in $workdir\node_modules\oauth\lib\oauth2.js as method _executeRequest exists only there.
If you have trouble finding _executeRequest I recommend do full search in node_modules\oauth as I found that method in oauth2.js
Also, don't forget that if you use third party API that you should consider them as well. I use vkAPI SDK, which is basically a wrapper over http_library with handy methods.
In that case I would suggest to wrap http_library calls and decorate them for your need.
Good way to start here Anyway to set proxy setting in passportjs?
I have passport working with Google, and Facebook. I have attempted to add Github to add these credentials so I can do validated Github API calls. So I simply added the same pattern I used to login using either Google or Facebook credentials.
BUT I see InternalOAuthError in the middle of my code after the auth callback has taken place from Github. This happens when the nearly last line: 'return done(null, user.userData);' is called. Attempting to debug interferes with the callbacks. SO I am hoping that someone with greater clarity about passport can explain what I am doing wrong.
What is really strange is that I have already received the user profile from github and stored in in my database with 'user.update(db)', in the same way I do with google. Then the crash happens when I attempt to return by calling done(...).
Do I need to add something to my profile on github? or something else?
Or is this because I already have used passport much earlier to login using Google credentials. Note that for Google, or Facebook, I have specified session: false. I have attempted this with both 'passport-github' and 'passport-github2'.
The code for clarity is:
index.js
var express = require('express');
var passport = require('passport');
var auth = require('../auth.service');
var router = express.Router();
router
.get('/:user', passport.authenticate('github', {
failureRedirect: '/signup',
session: false
}))
.get('/callback', passport.authenticate('github', {
failureRedirect: '/signup',
session: true
}), auth.setTokenCookie);
module.exports = router;
and the corresponding passport.js
var passport = require('passport');
var GitHubStrategy = require('passport-github2').Strategy;
var monk_db = rekuire('server/monk_db');
var loggingAndErrors = rekuire('./loggingAndErrors');
var auth = require('../auth.service');
var config = require('../../config/environment');
var jwt = require('jsonwebtoken');
var expressJwt = require('express-jwt');
var validateJwt = expressJwt({ secret: config.secrets.session });
var jwtDecode = require('jwt-decode');
var ObjectID = require("bson-objectid");
exports.setup = function (User, config) {
passport.use(new GitHubStrategy({
clientID: config.github.clientID,
clientSecret: config.github.clientSecret,
callbackURL: config.github.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
//loggingAndErrors.logger.log(accessToken);
var token = req.cookies.token;
var decoded = jwtDecode(token);
var user_id = decoded._id;
var db = monk_db.getDb();
var users = User.getUsers(db);
users.findById(ObjectID(user_id), function(err, user) {
if (err) {
loggingAndErrors.loggerError.log(err);
return done(err);
}
//loggingAndErrors.logger.log(profile);
user.github=profile._json
user = User.newUser(user);
user.update(db).onResolve(function(err, result) {
if (err) {
loggingAndErrors.loggerError.log(err);
return done(err);
}
//loggingAndErrors.logger.log(user);
loggingAndErrors.logger.log("calling done(err, user) for user_id:", user.userData._id);
return done(null, user.userData);
});
});
}
));
};
The crash is:
{ statusCode: 404,
data: '{"message":"Not\
Found","documentation_url":"https://developer.github.com/v3"}' }
GET /auth/github/callback?code=7c0c6dff81cdd9417301 500 945.444 ms - 674
InternalOAuthError: Failed to fetch user profile
at /home/joel/workspace/Tracker2/node_modules/passport-github2/lib/strategy.js:118:21
at passBackControl (/home/joel/workspace/Tracker2/node_modules/passport-github2/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:123:9)
at IncomingMessage.<anonymous> (/home/joel/workspace/Tracker2/node_modules/passport-github2/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:142:7)
at IncomingMessage.emit (events.js:129:20)
at _stream_readable.js:908:16
at process._tickDomainCallback (node.js:381:11)
I just ran into the same problem, so here is what works for me.
Use passport-github2 and request access to the user's email address when authenticating:
passport.authenticate('github', { scope: [ 'user:email' ] }));
There are other permissions that you may want to access at the same listed in the GitHub OAuth documentation.