I'm trying to combine ExpressJS + PassportJS with Angular SPA application. The problem probably is in CORS headers.
Here is what I'm trying to accomplish:
Client View:
<a class="btn btn-block btn-social btn-twitter" ng-click="twitter()">
<i class="fa fa-twitter"></i> Sign in with Twitter
</a>
Client Controller:
Is the following approach correct?
$scope.twitter = function() {
$http.get('oauth/twitter').then(function(res) {
// Fetch the user from response and store it somewhere on the client side
});
};
The server side responsible for handling authentication looks as follows:
Server routes:
app.get('/oauth/twitter', passport.authenticate('twitter', {
failureRedirect: '/signin'
}));
app.get('/oauth/twitter/callback', passport.authenticate('twitter', {
failureRedirect: '/signin',
successRedirect: '/'
}));
Twitter strategy:
passport.use(new TwitterStrategy({
consumerKey: config.twitter.clientID,
consumerSecret: config.twitter.clientSecret,
callbackURL: config.twitter.callbackURL,
passReqToCallback: true
}, function (req, token, tokenSecret, profile, done) {
var providerData = profile._json;
providerData.token = token;
providerData.tokenSecret = tokenSecret;
var providerUserProfile = {
fullName: profile.displayName,
username: profile.username,
provider: 'twitter',
providerId: profile.id,
providerData: providerData
};
users.saveOAuthUserProfile(req, providerUserProfile, done);
}));
Users Server Controller:
exports.saveOAuthUserProfile = function (req, profile, done) {
User.findOne({
provider: profile.provider,
providerId: profile.providerId
}, function (err, user) {
if (err) {
return done(err);
} else {
if (!user) {
var possibleUsername = profile.username || ((profile.email) ? profile.email.split('#')[0] : '');
User.findUniqueUsername(possibleUsername, null, function (availableUsername) {
profile.username = availableUsername;
user = new User(profile);
user.fullName = profile.fullName;
user.save(function (err) {
if (err) {
var message = getErrorMessage(err);
req.flash('error', message);
return done(err);
}
return done(err, user);
});
});
} else {
return done(err, user);
}
}
});
};
When I click Sign in with Twitter in a browser (Chrome/Firefox) I see the following requests being made:
GET http://localhost:3000/oauth/twitter (302 Moved Temporarily)
GET https://api.twitter.com/oauth/authenticate?oauth_token=Zz014AAAAAAAg5HgAA******** (307 Internal Redirect)
The browser complains about missing "Access-Control-Allow-Origin" headers.
To fix this I have tried using cors package like this
var cors = require('cors');
api.use(cors());
and also manually setting headers of all responses, with no effect.
Could you please show me the way?
Regards
Instead of using cors module,try to set header in nodejs
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
I faced with a similar problem and couldn't fix it within the code. So I found this Chrome extension: Allow-Control-Allow-Origin: *
You can set URL patterns and enable Cross-Origin Resource Sharing.
Don't forget to disable it when you finish your work!
Related
Trying to authenticate with passport via AngularJS. I've found a lot of threads saying that the issue is the default Content-Type of the request. Tried a couple of ways and I always get a 400 Error (Bad Request).
$http.post('/login', {
username: $scope.user.username,
password: $scope.user.password
})
.success(function(user) {
console.log('success');
// $location.url('/');
})
.error(function() {
console.log('failed');
// location.url('/login')
})
[Snipped some stuff that I don't think is the issue]
UDPATE: I think this might be an issue with passport. Using the body-parser middleware, I'm able to tell that I'm sending the data in the following format:
{ username: me, password: 12345 }
Alas, I still get the error code 400.
var bodyParser = require('body-parser');
var jsonParser = bodyParser.json();
app.post('/login', jsonParser, function(req,res) {
if (!req.body) {
console.log('no body');
return res.sendStatus(400);
}
console.log(req.body);
res.end(req.user);
});
Here's the local strategy
passport.use(new LocalStrategy(
function(username, password, done) {
if (username === "me" && password === "123") {
return done(null, {name: "me"});
}
return done(null, false, {message: 'Incorrect username or password.'});
}
));
And the route for when I try to get passport to work.
app.post('/login',passport.authenticate('local'), function(req,res) {
console.log('login in server.js')
res.send(req.user);
});
Pretty sure this is all standard, based on everything I've read.
Needed this...
var bodyParser = require('body-parser');
app.use(bodyParser.json());
I created a node express RESTful API with jsonwebtoken as authentication method. But unable to pass the x-access-token as headers using angular js.
my JWT token authentication script is,
apps.post('/authenticate', function(req, res) {
// find the item
Item.findOne({
name: req.body.name
}, function(err, item) {
if (err) throw err;
if (!item)
{
res.json({ success: false, message: 'Authentication failed. item not found.' });
}
else if (item)
{
// check if password matches
if (item.password != req.body.password)
{
res.json({ success: false, message: 'Authentication failed. Wrong password.' });
}
else
{
// if item is found and password is right
// create a token
var token = jwt.sign(item, app.get('superSecret'), {
expiresIn: 86400 // expires in 24 hours
});
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
}
});
});
Middleware which checks the token is correct is,
apps.use(function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.params.token || req.headers['x-access-token'];
// decode token
if (token)
{
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err)
{
return res.json({ success: false, message: 'Failed to authenticate token.' });
}
else
{
// if everything is good, save to request for use in other routes
req.decoded = decoded;
next();
}
});
}
else
{
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
Finally the GET method script is,
app.get('/display', function(req, res) {
Item.find({}, function(err, items) {
$http.defaults.headers.common['X-Access-Token']=token;
res.json(items);
});
});
But it always failed to authenticate. Please any one help me to solve this issue. I am really stucked here.
It always shows only the following authentication failed message.
{"success":false,"message":"No token provided."}
If you use $http as the dependency in your angular controller then this would help you I guess -
var token = this.AuthToken.getToken();
$http.get('/api/me', { headers: {'x-access-token': token} });
I will change this according to your code once you upload your angular code.
The client should be sending the token in the Authorization header, using the Bearer scheme, as 'X-' headers have been deprecated since 2012:
Your node would now be along the lines of:
apps.post('/authenticate', function(req, res) {
.....
var token = 'Bearer' + ' ' + jwt.sign(item, app.get('superSecret'), {
expiresIn: 86400 // expires in 24 hours
});
.....
}
apps.use(function(req, res, next) {
// Trim out the bearer text using substring
var token = req.get('Authorization').substring(7);
....
}
Then your angular code would become:
var token = this.AuthToken.getToken();
$http.get('/api/me', { headers: {'Authorization': token} });
You could create a interceptor that catches all ajax calls and injects the token into the header. That way you would not have inject it every time you make an ajax call.
This is a good place to start if you wanted to go that route:
http://www.webdeveasy.com/interceptors-in-angularjs-and-useful-examples/
I'm facing a problema with social signup because its not logging after its created as in local signup. When redirects, api/users/me is not accessible..is unauthorized (401), different from what i get in local signup, that redirects with user information.
in facebook/index.js I have default gets
.get('/', passport.authenticate('facebook', {
scope: ['email', 'user_about_me'],
failureRedirect: '/signup',
session: false
}))
.get('/callback', passport.authenticate('facebook', {
failureRedirect: '/signup',
session: false
}), auth.setTokenCookie);
and in auth.service.js I have the default functions
function isAuthenticated() {
return compose()
// Validate jwt
.use(function(req, res, next) {
// allow access_token to be passed through query parameter as well
if (req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.query.access_token;
}
validateJwt(req, res, next);
})
// Attach user to request
.use(function(req, res, next) {
User.findByIdAsync(req.user._id)
.then(function(user) {
//user here is undefined, it shouldn't be. i want to find out why.
if (!user) {
return res.status(401).end();
}
req.user = user;
next();
})
.catch(function(err) {
return next(err);
});
});
}
function signToken(id, role) {
return jwt.sign({ _id: id, role: role }, config.secrets.session, {
expiresInMinutes: 60 * 5
});
}
/**
* Set token cookie directly for oAuth strategies
*/
function setTokenCookie(req, res) {
if (!req.user) {
return res.status(404).send('Something went wrong, please try again.');
}
var token = signToken(req.user._id, req.user.role);
res.cookie('token', token);
res.redirect('/');
}
Could anyone help me with this...Am I missing something?
Thanks in advance.
I had the similar issue. I had modified Angular full-stack code with a customizable backend URL.
https://github.com/kannach/AFSPhonegap/blob/master/client/components/services/config.service.js
I was testing the facebook login in localhost, whereas my backend url was pointing to my production server. Once I chanded the backed url to localhost, then everything worked as expected
Solution here: Trouble with getting data from nodejs/express because of CORS
So i have a problem with getting data from nodejs/express because of CORS.
I'm getting error something like this (sorry, error i translated with google):
Query from an external source is blocked: one source of policy prohibits reading remote resource on http://localhost:8080/api/login. (Cause: Failed to query CORS).
I'm trying to send query with angular $http.post
$http.post($rootScope.api + 'login', {
email: form.email.$viewValue,
password: form.password.$viewValue
}).then(function(data) {
console.log(data.data);
});
Here is my server code (i cut some code, because i think it not important):
/* some nodejs requires */
var cors = require('express-cors');
mongoose.connect('mongodb://localhost:27017/batarindashboard');
require('./config/passport')(passport);
/* some express settings */
app.use(cors({
allowedOrigins: [
'localhost:*', '127.0.0.1:*'
]
}));
var port = process.env.PORT || 8080;
var router = express.Router();
router.use(function(req, res, next) {
console.log('Something is happening');
next();
});
/* sign up api */
router.post('/login', passport.authenticate('local-login', {
failureRedirect : '/api/passport_message',
failureFlash : true
}), function(req, res) {
res.json({
'message': 'You successfully signed in!'
});
});
/* passport_message api */
/* is_logged_in api */
/* logout api */
app.use('/api', router);
app.listen(port);
console.log('Magic happens on port ' + port);
And passport file (maybe problem is here. Why not?)
var LocalStrategy = require('passport-local').Strategy;
var User = require('./../app/models/user');
module.exports = function(passport) {
/* some serialize deserialize code */
/* sign up strategy */
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, function(req, email, password, done) {
User.findOne({
'local.email':email
}, function(err, user) {
if(err) return done(err);
if(!user) {
return done(null, false, req.flash('message', 'No user found.'));
}
if(!user.validPassword(password)) {
return done(null, false, req.flash('message', 'Oops! Wrong password.'));
}
return done(null, user);
})
}))
};
Trying to use passport.js in my Angular app to use google authentication.
The way I have my express endpoints set up is:
app.set('client-google-signin-path', '/google');
app.route('/auth/google')
.get(function(req, res, next) {
var mobileApp = req.headers.origin;
passport.authenticate('google', { scope: ['profile', 'email'], callbackURL: mobileApp + req.app.get('client-google-signin-path')})(req, res, next);
});
app.route('/auth/google/callback')
.get(function(req, res, next) {
//NEVER MAKES IT HERE
var mobileApp = req.headers.origin;
passport.authenticate('google', {scope: ['profile', 'email'], callbackURL: mobileApp + req.app.get('client-google-signin-path')},
function(err, user, info) {
var profile;
if(err) return next(err);
if(!info || !info.profile) return res.status(400).send("Profile not available.");
// Grab the profile info returned from google.
profile = info.profile._json;
//model logic
User.findOne({ 'email': profile.email }, function(err, user) {
if(err) return next(err);
if(!user) return res.status(400).send("There does not appear to be an account that matches, you may need to create an account and try again.");
req.logIn(user, function(err) {
if(err) return next(err);
return res.status(200).send(user);
});
});
res.status(200);
})(req, res, next);
});
My controller looks like:
$scope.signInGoogle = function() {
return $http.get(backendConst.location + 'auth/google')
.success(function(url) {
// Redirect to google.
$window.location.href = url;
});
};
$scope.signInGoogleCallback = function() {
return $http.get(backendConst.location + 'auth/google/callback', {
params: $location.search()
})
.success(function(user) {
$scope.gRes = 'Success!';
$location.search('code', null);
$location.hash(null);
AuthModel.setUser(user);
$location.path('/');
})
.error(function(err) {
$scope.gRes = 'Authentication failed, redirecting.';
$location.path('/authenticate');
});
And for good measure, my dinky little /google redirect page is:
<div class="container clearfix" ng-init="signInGoogleCallback()">
<h1>Google YO!</h1>
<h4>{{gRes}}</h4>
</div>
So what's happening is that it brings in the "log in with google" page, lets me select my account, then when it calls the callback it quickly flashes the /google view but then kicks me back out into /authenticate, the view I started in. Doing some debugging shows that
return $http.get(backendConst.location + 'auth/google/callback', {
params: $location.search()
})
is throwing a blank "err" even though in my endpoint logs it's saying that /auth/google/callback returned a 200. I'm not sure what's happening. I know this looks needlessly complicated but I do it this way with facebook and passport.js and everything works fine, except my endpoint logs are showing that with facebook the parameter "?code=" is being added to the /auth/facebook/callback url, like I'm trying to do with {params: $location.search()} in my google callback function in my controller. Debugging into the endpoint shows that it never makes it to where I left the //NEVER MAKES IT HERE comment, but when removing the {params: $location.search()} option it makes it passport.authenticate but not beyond. I'm completely stumped. Again I should stress that I handle facebook authentication the exact same way and it works perfectly.