I'm implementing a server that handles chat messages. In some cases I want to access data from a JIRA instance. I'm using passport-atlassian-oauth strategy for authenticating with JIRA and BearerStrategy for requests, but my issue is that the authentication is only valid in the browser after a user has given "My Server" read and write access to JIRA. In many guides they just call res.redirect('/successfulLogin') or something similar after a successful authentication, but I would instead like to do a rest call to JIRA, process the data and send it to my connected client application.
How do I do that?
I'm completely new to all this and everything just spins around in my head. I save and have access to the token used for authentication and when I for instance navigate to .../test/access_token=?[token] in my browser it works.
passport.use(new BearerStrategy(
function(token, done) {
// Find user by token
client.smembers('access_token:' + token, function(err, replies) {
if (err) {
return done(err);
}
// if user found
// TODO: yet again, hard coded for one
if (replies.length > 0) {
console.log('SHOULD BE 1:', replies[0]);
client.hgetall('users:' + replies[0], function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false);
}
return done(null, user, {scope: 'all'});
});
}
});
}
));
As you can see it's hard coded for just one user and I'm using Redis as a "database".
passport.use(new AtlassianOAuthStrategy({
applicationURL: 'http://localhost:2990/jira',
callbackURL: '/auth/atlassian-oauth/callback',
consumerKey: RsaPublicKey,
consumerSecret: rsaPrivateKey,
clientId: 'MyBot'
},
function(accessToken, tokenSecret, profile, done) {
// Find user
client.hgetall('users:1', function(err, user) {
if(err) {
return done(err);
}
// user not found
if(!user) {
// create new user, no worries!
// TODO: HARD CODED FOR ONE USER
client.hmset('users:1', 'id', profile.id, 'access_token', accessToken, function(err, res) {
client.sadd('id:admin', '1');
client.sadd('access_token:'+ accessToken, '1');
client.hgetall(profile.id, function(err, user) {
return done(null, user);
});
});
} else {
// Update access token!
client.hmset(profile.id, 'access_token', accessToken, function() {
client.sadd('access_token:' + accessToken, '1', function() {
client.hgetall(profile.id, function(err, result) {
return done(null, user);
});
});
});
}
});
}
));
Here's the rest
app.get('/auth/atlassian-oauth',
passport.authenticate('atlassian-oauth', {session: false, scope: []}),
function(req, res) {
console.log('- Function: /auth/atlassian-oauth - should not be called)');
});
app.get('/auth/atlassian-oauth/callback',
passport.authenticate('atlassian-oauth', {session: false, failureRedirect: '/login'}),
function(req, res) {
console.log('- Function: /auth/atlassian-oauth/callback - Authentication successful!', req.user.access_token);
// Update access token!
// Should I even do this? Shouldn't I already have the correct token?
client.hmset('users:1', 'access_token', req.user.access_token, function() {
client.sadd('access_token:' + req.user.access_token, '1', function() {
client.hgetall('users:1', function(err, result) {
res.redirect('/test?access_token=' + req.user.access_token);
});
});
});
});
So now that you've seen some relevant (just tell me and I'll post more) code, how do I send a rest call to JIRA without getting a 401? :)
EDIT: Any help appreciated! You would make me really happy if you just can point me into the right direction!
Ok. I figured it out! First of all you want to save both you access token and token secret to you db in AtlassianOAuthStrategy. Second, in order to send a REST call to a third party service you can just use http request with OAuth:
var request = require('request');
var oauth = {
signature_method : 'RSA-SHA1',
consumer_key : RsaPublicKey,
private_key : rsaPrivateKey,
token : [get access_token from you db],
token_secret : [get token_secret from you db]'
};
var url = 'http://localhost:2990/jira/rest/api/2/issue/' + id;
request.get({url:url, oauth:oauth, json:true}, function (e, r, issue) {
console.log(issue)
});
Now that everything is working I'm going to start refactoring and reading some more documentation in order to make the design prettier and figure out how to use Redis properly :)
Related
I am implementing an auth0 login flow for a node js server that serves a react app. I have implemented the login and log out flows correctly, but in the /callback URL I am given a false token that cannot be decrypted.
So, if I visit /login, it takes me to the Auth0 form correctly; and I can log in successfully, according to the logs in Auth0. But when Auth0 redirects back to the callback url, I get a false token.
app.get("/callback", (request, response, next) => {
passport.authenticate("auth0", (auth0Error, token) => {
if (!token){
// HERE, token is false
}
...
})(request, response, next);
});
What could cause this false token? How does the authenticate function work in a callback? And how should I handle this? Should I try auth0 again?
To understand the issue, I started the project in debug mode and added some breakpoint. If you look carefully, passport.authenticate() method calls Auth0Strategy . Then, Auth0Strategy exchange code for token (Authorization Code flow) and returns user profile. Therefore, in the the callback route , token is not available. I added the following lines of code to access accessToken in the callback route.
const strategy = new Auth0Strategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: '/callback',
},
function(accessToken, refreshToken, extraParams, profile, done) {
// accessToken is the token to call Auth0 API (not needed in the most cases)
// extraParams.id_token has the JSON Web Token
// profile has all the information from the user
console.log(JSON.stringify(profile))
console.log(accessToken);
return done(null, profile, accessToken); // Passing access token
}
);
Then , callback route should have accessToken available.
router.get('/callback', function (req, res, next) {
passport.authenticate('auth0', function (err, user, token) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function (err) {
if (err) { return next(err); }
const returnTo = req.session.returnTo;
delete req.session.returnTo;
res.redirect(returnTo || '/user');
});
})(req, res, next);
});
This was added just to explain the flow. But you can ignore passing the token for production application.
I am trying to set up a simple authentification system with Parse Server:
app.js
...
app.get('/login', (req, res) => {
res.render('login.ejs');
});
app.post('/login', (req, res) => {
console.log('POST /login\t' + util.inspect(req.body));
driver.login(req, (err, user) => {
//Here, user is defined
if(err) {
res.redirect('/login');
} else {
res.redirect('/user');
}
});
});
...
driver.js:
...
function login(req, callback) {
var username = req.body.username,
password = req.body.password;
Parse.User.logIn(username, password, {
success: (user) => {
callback();
},
error: (user, error) => {
callback(JSON.stringify(error));
}
});
}
function isLoggedIn(req, callback) {
console.log('isLoggedIn?');
console.log(util.inspect(req.user)); //undefined
if(req.user) {
callback();
} else {
callback('Not logged in');
}
}
...
When I access /login, I can login just fine, and get redirected to /user without any error, but on /user, which use isLoggedIn as a middleware, req.user is undefined.
I have seen others with the same problem when searching, but the post where either old (<2015), using another part of the JSSDK (react/browser), or just didn t get any answer.
I know I could use session, and recreate the user each time based on that, but it feels really hackish, is it really the supported way?
You have two routes to go, either have a REST-full server, which means users are not persistent between route calls, STATE-full and use sessions.
Luckily there is a really good nodejs authentication middleware already build that will handle all this session managment. This is called passportJS. Documentation can be found here: http://passportjs.org/docs
You can not only have authentication through local logins, but have support for authentication with google, facebook, github and many more. This is done through what are called Strategies. You use google-strategy for having google oauth, facebook-stradegy for facebook oauth, etc.
What you would be looking for is a local-strategy, which is called this because you want to authenticate with local user credentials. This strategy can be found here: https://www.npmjs.com/package/passport-local
you will need both passport and passport local and to install simply run
npm install passport passport-local
From there, just go through the documentation I have linked above for how to set everything up.
To develop my answer to blablabla comment, who asked what I ended up using.
In my case, I was develloping a REST API, and the session data wasn't expected to change once connected.
So what I did is delegate the session to the client, and go full stateless, by using JWT.
When the client is authentified, I encrypt his session data in a Json Web Token, and send him. When he try to access a protected page, he send me the JWT, which I can decrypt, and recreate req.user based on the information within.
My middleware look like this:
function isLoggedIn(req, res, next) {
//If there is a token
if(req.headers != null && req.headers.token != null) {
//Verify it and extract the user's data
verify(req.headers.token, (err, user) => {
if(err != null) {
res.status(401);
res.json({error: err});
} else {
//Recreate req.user
req.user = user;
next();
}
});
} else {
res.status(401);
res.json({error: 'No token'});
}
}
function verify(token, callback) {
//Verify the token
jwt.verify(token, jwtSecret, (error, user) => {
if(error != null) {
callback(error);
} else {
Separately check if logged into Parse
parse.isLoggedIn(user, (error, loggedIn) => {
if(error != null) {
callback(error);
} else if(!loggedIn) {
callback('Not logged in the Parse');
} else {
callback(null, user);
}
});
}
});
}
I've been taking courses and watching tutorials on NodeJS for awhile and decided to put them to good use in an app.
For this project I need users to signup and login in order to store their activity in a database. I used Passport to do this process, the code for this section of the project is this:
/****** Passport functions ******/
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
db.user.findOne( { where : { idUser : id } }).then(function (err, user) {
done(err, user);
});
});
//Facebook
passport.use(new FacebookStrategy({
//Information stored on config/auth.js
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: configAuth.facebookAuth.callbackURL,
profileFields: ['id', 'emails', 'displayName', 'name', 'gender']
}, function (accessToken, refreshToken, profile, done) {
//Using next tick to take advantage of async properties
process.nextTick(function () {
db.user.findOne( { where : { idUser : profile.id } }).then(function (err, user) {
if(err) {
return done(err);
}
if(user) {
return done(null, user);
} else {
db.user.create({
idUser : profile.id,
token : accessToken,
nameUser : profile.displayName,
email : profile.emails[0].value,
sex : profile.gender
});
return done(null);
}
});
});
}));
app.use(express.static(__dirname + '/public/'));
/* FACEBOOK STRATEGY */
// Redirect the user to Facebook for authentication. When complete,
// Facebook will redirect the user back to the application at
// /auth/facebook/callback//
app.get('/auth/facebook', passport.authenticate('facebook', { scope : ['email']}));
/* FACEBOOK STRATEGY */
// Facebook will redirect the user to this URL after approval. Finish the
// authentication process by attempting to obtain an access token. If
// access was granted, the user will be logged in. Otherwise,
// authentication has failed.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { successRedirect: '/app',
failureRedirect: '/' }));
app.get('/', function (req, res) {
res.render('/');
});
app.get('/app', isLoggedIn, function (req, res) {
res.sendFile('app.html');
});
function isLoggedIn(req, res, next) {
if(req.isAuthenticated()) {
return next();
} else {
res.redirect('/');
}
}
The tutorial I followed on Facebook Auth using Passport used pretty much the same code, I changed the User model because the tutorial used Mongoose and I'm using Sequelize but this aspect is working great, when I click to signup with FB it registers me or logs me in, the queries do the work.
However, what isn't working is the redirection. When I register using facebook, it gets stuck and doesn't load anything (wheel keeps spinning on index.html (where the FB button is) and doesn't load anything). When I login using facebook, it only displays this on the screen:
[object SequelizeInstance:user]
On the tutorial, the instructor used EJS as a template language,however I already built 95% of the front end of the project using HTML, CSS and jQuery (yeah, should have used React or Angular but time is sensitive and was already learning Node). I believe this is one of the reasons this is happening but I'm not 100% sure on what is going on here and why I'm getting the error or how to get around.
Any help is appreciated, if more information / code is needed, let me know. Thank you
So after a good amount of time debugging and with some good help, I figured out what was causing my problem, there were actually three errors in there.
First of all, in the Facebook Strategy, this is how I should had built it:
passport.use(new FacebookStrategy({
//Information stored on config/auth.js
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: configAuth.facebookAuth.callbackURL,
profileFields: ['id', 'emails', 'displayName', 'name', 'gender']
}, function (accessToken, refreshToken, profile, done) {
//Using next tick to take advantage of async properties
process.nextTick(function () {
db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
if(err) {
return done(err);
}
if(user) {
return done(null, user);
} else {
//Create the user
db.user.create({
idUser : profile.id,
token : accessToken,
nameUser : profile.displayName,
email : profile.emails[0].value,
sex : profile.gender
});
//Find the user (therefore checking if it was indeed created) and return it
db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
if(user) {
return done(null, user);
} else {
return done(err);
}
});
}
});
});
}));
The callback after db.user.findOne had switched parameters, so it was giving me an error every time even though it didn't have one, so I switched those and also added a query to look for the user in the DB after creating it to be able to return it.
On the second facebook route, this is how I built it:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('../../app.html');
});
This allowed me to continue using HTML (I'll probably rewrite it to use a better view later on), and on testing, I was able to get the information from req.user.
Finally, I had a minor naming error on Passport's serializeUser:
passport.serializeUser(function (user, done) {
done(null, user.idUser);
});
Just changing from user.id to user.idUser to maintain the naming convention I used.
Hopefully this helps other people using Sequelize with Passport.
I am a implementing Oauth2 bearer strategy to authenticate clients using passport.js.
I use the passport-http-bearer package and implemented the following callback:
passport.use(new BearerStrategy(
function (accessToken, done) {
Token.findOne({ token: accessToken }).populate('client').exec(function (err, token) {
if (err) {
return done(err);
}
if (!token) {
return done(null, false);
}
if (new Date() > token.expirationDate) {
Token.remove(accessToken, function (err) {
return done(err);
});
}
if (token.client) {
var info = { scope: token.client.scopes };
return done(null, token.client, info);
}
else {
return done(null, false);
}
});
}
));
Later on I want to read the client info - which gets placed under the req.user key by the passport done() callback.
Is there a way to get passport to store the returned client in req.client variable instead of req.user?
My goal is to authenticate users and clients with the same application, therefore I can't have conflicting keys in my request.
thanks
You can pass the user property while initialising passport.
app.use(passport.initialize({userProperty:'client'));
passport will use this property to store info.
I would like to save the user object within the request object returned from facebook after successful authentication using passport to use in a different method. The purpose for this is to include in my linkschema all different users who post via the same link.
The flow should be as follows:
facebook authenticate
user object is stored somewhere [HOW DO I DO THIS PART???]
link is clicked, gets routed to post method whose parameters are the link and userid.
(If the link exists, the users are appended to the array of users defined in my linkschema)
/ =====================================
/ / FACEBOOK ROUTES
// =====================================
// route for facebook authentication and login
passport.use(new FacebookStrategy({
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: "http://localhost:3000/auth/facebook/callback/"
},
function(accessToken, refreshToken, profile, done) {
UserSchema.AddUnique(profile, accessToken, function(err, user) {
if (err) {
return done(err);
}
return done(null, user);
});
}
));
// Redirect the user to Facebook for authentication. When complete,
// Facebook will redirect the user back to the application at
// /auth/facebook/callback
router.get('/auth/facebook', passport.authenticate('facebook', {
scope: 'email'
}));
// Facebook will redirect the user to this URL after approval. Finish the
// authentication process by attempting to obtain an access token. If
// access was granted, the user will be logged in. Otherwise,
// authentication has failed.
var user = router.get('/auth/facebook/callback',
passport.authenticate('facebook', {
failureRedirect: '/login'
}), function(req, res) {
var user = req.user;
res.redirect('/browse');
return function() {
return user;
}();
});
function user() {
console.log('in here');
console.log(user);
}
router.use(function(err, req, res, next) {
console.log(err)
next(err)
});
router.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
Thank you in advance!
Store them outside in an object
var Users = {};
passport.use(new FacebookStrategy({…},
function(accessToken, refreshToken, profile, done) {
UserSchema.AddUnique(profile, accessToken, function(err, user) {
if (err) {
return done(err);
}
// Push the user into that object
Users[user.id] = user;
return done(null, user);
});
}
));
function user() {
console.log(Users) //=> { '5234…': {…} , '5345…': {…} , … }
}