I have a stand alone oauth2 identity provider that is working.
Now I'm developing a consumer that will authenticate users with this stand alone provider.
I'm following this tutorial about passport and Google Auth:
I'm trying to use this information to use passport-oauth2 to work as a client. I have made some changes in the code provided in the tutorial above by following the official documentation on passoprt-oauth2.
I think that I have some problem in the callback function where expressjs receive the confirmation of authentication and info about the user. I don't understand how to use this information.
Here is the code of my app.js
const express = require('express');
const app = express();
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2');
const cookieSession = require('cookie-session');
// cookieSession config
app.use(cookieSession({
maxAge:24*60*60*1000,
keys: ['secret-personalize']
}));
app.use(passport.initialize());
app.use(passport.session());
//Strategy config
passport.use(new OAuth2Strategy({
authorizationURL: 'http://localhost:3000/dialog/authorize',
tokenURL: 'http://localhost:3000/oauth/token',
clientID: 'xyz123',
clientSecret: 'ssh-password',
callbackURL: "/auth/oauth2/callback"
},
(accessToken, refreshToken, profile, done) => {
console.log(profile);
done(null, profile);
}
));
// Used to decode the received cookie and persist session
passport.deserializeUser((user, done) => {
done(null, user);
});
// Middleware to check if the User is authenticated
app.get('/auth/oauth2',
passport.authenticate('oauth2'));
function isUserAuthenticated(req, res, next){
if (req.user){
next();
} else {
res.send('you must login!');
}
}
// Routes
app.get('/', (req, res) => {
res.render('index.ejs');
});
// The middleware receives the data from AuthPRovider and runs the function on Strategy config
app.get('/auth/oauth2/callback', passport.authenticate('oauth2'), (req,res) => {
res.redirect('/secret');
});
// secret route
app.get('/secret', isUserAuthenticated, (req, res) =>{
res.send('You have reached the secret route');
});
// Logout route
app.get('/logout',(req, res) => {
req.logout();
res.redirect('/');
});
app.listen(8000, () => {
console.log('Server Started 8000');
});
and this is for views/index.ejs
<ul>
<li>Login</li>
<li>Secret</li>
<li>Logout</li></ul>
I got this error:
Error: Failed to serialize user into session
at pass (/home/user/job/NodeJS/test-consumer/second/node_modules/passport/lib/authenticator.js:281:19)
at Authenticator.serializeUser (/home/user/job/NodeJS/test-consumer/second/node_modules/passport/lib/authenticator.js:299:5)
at SessionManager.logIn (/home/user/job/NodeJS/test-consumer/second/node_modules/passport/lib/sessionmanager.js:14:8)
at IncomingMessage.req.login.req.logIn (/home/user/job/NodeJS/test-consumer/second/node_modules/passport/lib/http/request.js:50:33)
at OAuth2Strategy.strategy.success (/home/user/job/NodeJS/test-consumer/second/node_modules/passport/lib/middleware/authenticate.js:248:13)
at verified (/home/user/job/NodeJS/test-consumer/second/node_modules/passport-oauth2/lib/strategy.js:177:20)
at OAuth2Strategy.passport.use.OAuth2Strategy [as _verify] (/home/user/job/NodeJS/test-consumer/second/app.js:31:5)
at /home/user/job/NodeJS/test-consumer/second/node_modules/passport-oauth2/lib/strategy.js:193:24
at OAuth2Strategy.userProfile (/home/user/job/NodeJS/test-consumer/second/node_modules/passport-oauth2/lib/strategy.js:275:10)
at loadIt (/home/user/job/NodeJS/test-consumer/second/node_modules/passport-oauth2/lib/strategy.js:345:17)
Every help is welcome.
Thanks you
You need to add serializer:
passport.serializeUser(function(user, done) {
done(null, user);
});
I'm using this module now, but the profile always returns empty.
First you need to override the userProfile
This is the source code
const passport = require('passport')
// const { Strategy: GoogleStrategy } = require('passport-google-oauth20')
const { Strategy: GithubStrategy } = require('passport-github')
const { Strategy: OAuth2Strategy } = require('passport-oauth2')
const { GITHUB_CONFIG, OAUTH2_CONFIG} = require('../config')
const Profile = require('./profile')
module.exports = () => {
// Allow passport to serialize and deserialize users into sessions
passport.serializeUser((user, cb) => cb(null, user))
passport.deserializeUser((obj, cb) => cb(null, obj))
// The callback that is invoked when an OAuth provider sends back user
// information. Normally, you would save the user to the database
// in this callback and it would be customized for each provider
const callback = (accessToken, refreshToken, params, profile, cb) => {
console.log('access-token',accessToken)
console.log('refresh-token',refreshToken)
console.log('profile',profile)
console.log('params',params)
return cb(null, profile)
}
// Adding each OAuth provider's startegy to passport
// passport.use(new GoogleStrategy(GOOGLE_CONFIG, callback))
passport.use(new GithubStrategy(GITHUB_CONFIG, callback))
const DjangoStrategy = new OAuth2Strategy(OAUTH2_CONFIG, callback)
DjangoStrategy.userProfile = function(accessToken, done) {
var self = this;
this._userProfileURL = 'http://localhost:8001/accounts/profile/';
this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) {
var json;
if (err) {
if (err.data) {
try {
json = JSON.parse(err.data);
} catch (_) {}
}
if (json && json.message) {
return done(new APIError(json.message));
}
return done(new InternalOAuthError('Failed to fetch user profile', err));
}
try {
json = JSON.parse(body);
} catch (ex) {
return done(new Error('Failed to parse user profile'));
}
console.log('json', json)
var profile = Profile.parse(json);
profile.provider = 'oauth2';
profile._raw = body;
profile._json = json;
done(null, profile);
});
}
passport.use(DjangoStrategy)
}
Create a profile
profile.js
exports.parse = function(json) {
if ('string' == typeof json) {
json = JSON.parse(json);
}
var profile = {};
profile.id = String(json.id);
profile.displayName = json.name;
profile.username = json.username;
profile.email = json.email;
return profile;
};
You can also check clone my source code
https://github.com/faisallarai/nodejs-oauth-server.git
Related
Without passport, I have added simple middleware to authorize user as below.
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
const authHeader = req.get('Authorization');
const token = authHeader.split(' ')[1];
let jwttoken;
try {
jwttoken = jwt.verify(token, 'secret');
} catch (err) {
err.statusCode = 500;
throw err;
}
if (!jwttoken) {
const error = new Error('Not authenticated.');
error.statusCode = 401;
throw error;
}
req.userId = jwttoken.userId;
next();
};
with passport, I have added middleware as below
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'SECRET'
}
module.exports = (passport) => {
passport.use(new Strategy(options, async (payload, done) => {
await user.findByPk(payload.userId).then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
}).catch(error => {
return done(null, error);
});
}));
}
Question: like I was adding user to request without passport as req.userId = jwttoken.userId, how can we do it with passport middleware?
At passport this is accomplished with that line of code on your module.exports:
return done(null, user);
This tells passport that the information should be sent to the next function callback at req.user.
So for example, if you "user.findByPk(payload.userId)" response is something like:
{
"name": <NAME>
"profile": <profile>
}
At your protected endpoint's callback, you should see it on req.user.
For example:
app.post('/profile', passport.authenticate('jwt', { session: false }),
function(req, res) {
res.send(req.user.profile);
}
);
With req.user.profile being equal to of the "user.findByPk(payload.userId)" response.
So, I got everything to work, up until the routing doesn't seem to be getting the data I'm sending into the user. But, if console.log inside of passport, it spits out the correct information. So here is my passport code, which works for the most part:
const LocalStrategy = require('passport-local').Strategy;
const db = require('mongodb');
const bcrypt = require('bcryptjs');
const config = require('./config');
module.exports = async (passport) => {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// used to serialize the user for the session
passport.serializeUser((user, done) => {
done(null, user._id);
});
// used to deserialize the user
passport.deserializeUser(async(id, done) => {
let userData = await userDb().findOne({ '_id': id});
done(null, userData);
});
// Local Strategy login
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passReqToCallback: true,
}, async (req, username, password, done) => {
console.log('Pulled up: ' + username);
let userDb = await usersDb();
let userData = await userDb.findOne({ 'email': username})
// Check if user exists
if (userData === null) {
console.log('User doesn\'t exist');
return Promise.reject('Email or password incorrect.');
} else {
// if user exists check password
let passCheck = await bcrypt.compareSync(password, userData.password);
if (passCheck) {
console.log('Password Correct');
return done(null, userData);
} else {
// if password is wrong
console.log('Password incorrect');
return Promise.reject('Email or password incorrect.');
}
}
}));
// DB collection
async function usersDb() {
const client = await db.MongoClient.connect(
config.database,
{
useNewUrlParser: true
}
);
return client.db('kog').collection('users');
}
};
heres the login route:
router.post('/login',
passport.authenticate('local-login', {
successRedirect: '/game',
failureRedirect: '/',
}), (req, res) => {
});
But my issues lies here:
// Get game route
app.get('/game', async (req, res) => {
if (req.user) {
res.render('game');
} else {
console.log('Forced redirect');
res.redirect('/');
}
});
Thought of another block that may be of an issue:
app.get('*', async (req, res, next) => {
res.locals.user = await req.user || null;
next();
});
No matter what I do I seem to not be able to get the routing check to pull up the user data. I am not sure where I am going wrong here, as it works all up to that point. I will successfully "login" but will result in be being forcefully redirected to '/' even if everything as worked correctly.
I am fairly certain it is the fact I probably am not handling the async/await stuff correctly, but I'm not sure where I am having problems.
I think you are missing :
passport.authenticate('local')
Which triggers your passport.js file localStrategy. Not sure how you got the passport file running to check to do the console.
For more, refer: http://www.passportjs.org/docs/authenticate/
I am using express & jwt-simple to handle login/register & authenticated requests as a middleware api. I'm trying to create a .well-known endpoint so other api's can authenticate request based on token send in.
Here's my strategy:
module.exports = function() {
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = securityConfig.jwtSecret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
// User.where('id', jwt_payload.id).fetch({withRelated: 'roles'})
console.log('jwt_payload', jwt_payload)
User.where('id', jwt_payload.id).fetch()
.then(user => user ? done(null, user) : done(null, false))
.catch(err => done(err, false));
}));
};
Here's my login route:
router.post('/login', function(req, res) {
const {username, password} = req.body;
Promise.coroutine(function* () {
const user = yield User.where('username', username).fetch();
if(user) {
const isValidPassword = yield user.validPassword(password);
if (isValidPassword) {
let expires = (Date.now() / 1000) + 60 * 30
let nbf = Date.now() / 1000
const validatedUser = user.omit('password');
// TODO: Verify that the encoding is legit..
// const token = jwt.encode(user.omit('password'), securityConfig.jwtSecret);
const token = jwt.encode({ nbf: nbf, exp: expires, id: validatedUser.id, orgId: validatedUser.orgId }, securityConfig.jwtSecret)
res.json({success: true, token: `JWT ${token}`, expires_in: expires});
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
})().catch(err => console.log(err));
});
Here's my .well-known route:
router.get('/.well-known', jwtAuth, function(req, res) {
// TODO: look over res.req.user. Don't seem to be the way to get those parameters.
// We dont take those parameters from the decrypted JWT, we seem to grab it from the user in DB.
const { id, orgId } = res.req.user.attributes;
console.log("DEBUG: userId", id)
console.log("DEBUG: USER", res.req.user)
res.json({
success: true,
userId: id,
orgId
});
});
here's my jwtAuth() function:
const passport = require('passport');
module.exports = passport.authenticate('jwt', { session: false });
How would I actually get the token in the route function & decrypt it? All this does right now which works is that it authenticates if true however I need to be able to decrypt the token to send back the stored values. I'm not sure what res.req.user.attributes comes from, is this the token?
Take a look at passport-jwt and in your passport-config (or wherever you initialize passport) setup JWT Strategy:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const jwtAuth = (payload, done) => {
const user = //....find User in DB, fetch roles, additional data or whatever
// do whatever with decoded payload and call done
// if everything is OK, call
done(null, user);
//whatever you pass back as "user" object will be available in route handler as req.user
//if your user does not authenticate or anything call
done(null, false);
}
const apiJwtOptions: any = {};
apiJwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
apiJwtOptions.algorithms = [your.jwt.alg];
apiJwtOptions.secretOrKey = your.jwt.secret;
//apiJwtOptions.issuer = ???;
//apiJwtOptions.audience = ???;
passport.use('jwt-api', new JwtStrategy(apiJwtOptions, jwtAuth));
If you want just decoded token, call done(null, payload) in jwtAuth.
Then in your route files when you want to protect endpoints and have info about user, use as:
const router = express.Router();
router.use(passport.authenticate('jwt-api', {session: false}));
And in handler you should have req.user available. It is configurable to what property of req you store data from auth, req.user is just default.
I am new to nodejs. I need to create and authentication using nodejs. Here I found few answers but they also used old version. Here I am using these versions
I used this code to create the passport.js file in my node application.
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
const config = require('./db');
const User = require('./models/user');
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = config.secret;
module.exports = (passport)=>{
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findUserbyId({_id: jwt_payload._doc._id}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}));
};
And I used this routes method to create a route with the Authentication.
router.post('/profile',
passport.authenticate('jwt'),{ session: false },
(req, res)=> {
res.json({user:req.user});
});
I used postman to create a token and check the authentication.
I cannot understand the why it wrong. findUserById method is follow
module.exports.findUserbyId = (id, callback)=>{
User.findOne(id, callback);
}
What's the problem in my code? Why it every time gives
unauthorized
in my postman application.
I have three kind of user:
Viewer (link to sign in: auth/v/twitter)
Creator (link to sign in: auth/c/twitter)
Admin (link to sign in: auth/a/twitter)
And also I have 3 different db/collection
c_viewer
c_creator
c_admin
Where each kind of user have a different link to sign in.
Now let's take a look at the codes
var passport = require('passport')
,TwitterStrategy = require('passport-twitter').Strategy;
passport.use(new TwitterStrategy({
consumerKey: config.development.tw.consumerKey,
consumerSecret: config.development.tw.consumerSecret,
callbackURL: config.development.tw.callbackURL
},
function(token, tokenSecret, profile, done) {
process.nextTick(function(req, res) {
var query = User.findOne({ 'twId': profile.id});
query.exec(function(err, oldUser){
if(oldUser) {
done(null, oldUser);
} else {
var newUser = new User();
newUser.twId = profile.id;
newUser.twUsername = profile.username;
newUser.name = profile.displayName;
newUser.avatar = profile.photos[0].value;
-> newUser.age = req.body.creator.age; ???
newUser.save(function(err) {
if(err) throw err;
done(null, newUser);
});
};
});
});
}));
app.get('/auth/c/twitter', passport.authenticate('twitter'),
function(req, res) {
var userUrl = req.url;
// codes to pass the userUrl to TwitterStrategy
});
app.get('/auth/twitter/callback',
passportForCreator.authenticate('twitter', { successRedirect: '/dashboard', failureRedirect: '/' }));
And this is my form
<input type="text" name="creator[age]" placeholder="How old are you?">
<a id="si" class="btn" href="/auth/c/twitter">Sign in</a>
My questions:
1. Can We pass <input> data to the login process? so We can read the input data in TwitterStrategy, and save to the db
2. Can We get "c" from login url (auth/ c /twitter) and pass it to TwitterStrategy? so we can simply check in different db/collection and change the query.
The idea is to store your values before redirecting user on twitter for authentication, and re-use these values once the user came back.
OAuth2 includes the scope parameter, which perfectly suits that case. Unfortunately, TwitterStrategy is based on OAuth1. But we can tackle it !
The next trick is about when creating the user.
You should not do it when declaring strategy (because you cannot access input data), but a little later, in the last authentication callback
see here the callback arguments.
Declaring your strategy:
passport.use(new TwitterStrategy({
consumerKey: config.development.tw.consumerKey,
consumerSecret: config.development.tw.consumerSecret,
callbackURL: config.development.tw.callbackURL
}, function(token, tokenSecret, profile, done) {
// send profile for further db access
done(null, profile);
}));
When declaring your authentication url (repeat for a/twitter and v/twitter):
// declare states where it's accessible inside the clusre functions
var states={};
app.get("/auth/c/twitter", function (req, res, next) {
// save here your values: database and input
var reqId = "req"+_.uniqueId();
states[reqId] = {
database: 'c',
age: $('input[name="creator[age]"]').val()
};
// creates an unic id for this authentication and stores it.
req.session.state = reqId;
// in Oauth2, its more like : args.scope = reqId, and args as authenticate() second params
passport.authenticate('twitter')(req, res, next)
}, function() {});
Then when declaring the callback:
app.get("/auth/twitter/callback", function (req, res, next) {
var reqId = req.session.state;
// reuse your previously saved state
var state = states[reqId]
passport.authenticate('twitter', function(err, token) {
var end = function(err) {
// remove session created during authentication
req.session.destroy()
// authentication failed: you should redirect to the proper error page
if (err) {
return res.redirect("/");
}
// and eventually redirect to success url
res.redirect("/dashboard");
}
if (err) {
return end(err);
}
// now you can write into database:
var query = User.findOne({ 'twId': profile.id});
query.exec(function(err, oldUser){
if(oldUser) {
return end()
}
// here, choose the right database depending on state
var newUser = new User();
newUser.twId = profile.id;
newUser.twUsername = profile.username;
newUser.name = profile.displayName;
newUser.avatar = profile.photos[0].value;
// reuse the state variable
newUser.age = state.age
newUser.save(end);
});
})(req, res, next)
});