hi i am new to sails and passport js i am trying authentication using passport js in sails but while am adding passport.js in config folder it give an error..any one know how to resolve this issu..
Here is my error
C:\Users\sachinn\AppData\Roaming\npm\node_modules\sails\node_modules\include-all\lib\help-include-all-sync.js:281
throw e;
^
`include-all` attempted to `require(E:\myApi\config\passport.js)`, but an error occurred::
Details:TypeError: JwtStrategy requires a function to retrieve jwt from requests (see option jwtFromRequest)
at new JwtStrategy (E:\myApi\node_modules\passport-jwt\lib\strategy.js:39:15)
at Object.<anonymous> (E:\myApi\config\passport.js:68:3)
at Module._compile (module.js:570:32)
passort.js
/**
* Passport configuration file where you should configure strategies
*/
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var JwtStrategy = require('passport-jwt').Strategy;
var EXPIRES_IN_MINUTES = 60 * 24;
var SECRET = process.env.tokenSecret || "4ukI0uIVnB3iI1yxj646fVXSE3ZVk4doZgz6fTbNg7jO41EAtl20J5F7Trtwe7OM";
var ALGORITHM = "HS256";
var ISSUER = "nozus.com";
var AUDIENCE = "nozus.com";
/**
* Configuration object for local strategy
*/
var LOCAL_STRATEGY_CONFIG = {
usernameField: 'email',
passwordField: 'password',
passReqToCallback: false
};
/**
* Configuration object for JWT strategy
*/
var JWT_STRATEGY_CONFIG = {
secretOrKey: SECRET,
issuer : ISSUER,
audience: AUDIENCE,
passReqToCallback: false
};
/**
* Triggers when user authenticates via local strategy
*/
function _onLocalStrategyAuth(email, password, next) {
User.findOne({email: email})
.exec(function (error, user) {
if (error) return next(error, false, {});
if (!user) return next(null, false, {
code: 'E_USER_NOT_FOUND',
message: email + ' is not found'
});
// TODO: replace with new cipher service type
if (!CipherService.comparePassword(password, user))
return next(null, false, {
code: 'E_WRONG_PASSWORD',
message: 'Password is wrong'
});
return next(null, user, {});
});
}
/**
* Triggers when user authenticates via JWT strategy
*/
function _onJwtStrategyAuth(payload, next) {
var user = payload.user;
return next(null, user, {});
}
passport.use(
new LocalStrategy(LOCAL_STRATEGY_CONFIG, _onLocalStrategyAuth));
passport.use(
new JwtStrategy(JWT_STRATEGY_CONFIG, _onJwtStrategyAuth));
module.exports.jwtSettings = {
expiresInMinutes: EXPIRES_IN_MINUTES,
secret: SECRET,
algorithm : ALGORITHM,
issuer : ISSUER,
audience : AUDIENCE
};
Options passed to JwtStrategy constructor must have a key jwtFromRequest.
It is missing in your code.
See https://github.com/themikenicholson/passport-jwt#usage
jwtFromRequest (REQUIRED) Function that accepts a request as the only
parameter and returns either the JWT as a string or null. See
Extracting the JWT from the request for more details.
Related
I've a React app that uses Keycloak as a authentication service. Also I've a Nodejs rest api with endpoints secured by keycloak, so the React app sends JWT when needs call an api. In Keycloak admin console I created 1 public client with users and roles.
All works fine, but the only problems is when a I logout through admin console, or
from my React application berfore that expiration time, I still can call to my app with these token.
Why my backend app doesn't validate the token with server?
My node app uses keycloak-node-connect adapter and my keycloak.json is:
{
"client-id": "my-public-client",
"bearer-only": true,
"auth-server-url": "http://localhost:8180/auth",
"realm": "my-realm"
}
Solved
I can solved my probleam like suggested in Keycloak: Access token validation end point
keycloak.config.js
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var request = require('request');
const createError = require('http-errors');
let _keycloak;
var memoryStore = new session.MemoryStore();
function initKeycloak() {
if (_keycloak) {
console.log("Trying to init Keycloak again!");
return _keycloak;
}
else {
console.log("Initializing Keycloak...");
_keycloak = new Keycloak({ store: memoryStore });
return _keycloak;
}
}
function getKeycloak() {
if (!_keycloak) {
console.error('Keycloak has not been initialized. Please called init first.');
}
return _keycloak;
}
async function validateTokenKeycloak(req, res, next) {
if (req.kauth && req.kauth.grant) {
console.log('--- Verify token ---');
try {
var result = await _keycloak.grantManager.userInfo(req.kauth.grant.access_token);
//var result = await _keycloak.grantManager.validateAccessToken(req.kauth.grant.access_token);
if(!result) {
console.log(`result:`, result);
throw Error('Invalid Token');
}
} catch (error) {
console.log(`Error: ${error.message}`);
return next(createError.Unauthorized());
}
}
next();
}
module.exports = {
memoryStore,
initKeycloak,
getKeycloak,
validateTokenKeycloak
};
app.js
const express = require('express');
const createError = require('http-errors');
const dotenv = require('dotenv').config();
const session = require('express-session');
const keycloakConfig = require('./config/keycloak.config');
const app = express();
// Keycloak
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: true,
store: keycloakConfig.memoryStore
}));
const keycloak = keycloakConfig.initKeycloak();
app.use(keycloak.middleware());
app.use(keycloakConfig.validateTokenKeycloak);
app.use("/health", require('./routes/health.route'));
// 404 handler and pass to error handler
app.use((req, res, next) => {
next(createError(404, 'Not found'));
});
// Error Handler
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.send({
error : {
status : err.status || 500,
message : err.message
}
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server starter on port ${PORT}`);
});
I'm trying to authenticate users locally with Passport.js while not keeping session and using my own JWTokens.
I was following this tutorial:
Learn using JWT with Passport authentication
While also reading Passport.js documentation. I don't know what went wrong, but passport doesn't seem to notice that some functions are indeed functions.
I've got a Load function to select a User from the DB(mongo) given certain criteria(a user might logIn with e-mail or phone number).
load: function(options, cb) {
options.select = options.select || 'email phone';
return this.findOne(options.criteria)
.select(options.select)
.exec(cb);
}
I'm calling passport.authenticate in my routes:
// Controllers //
const Users = require('../../app/controllers/users');
...
...
app.post('/api/login', passport.authenticate('local', { failureRedirect: '/api/login' }), Users.login);
And here's my local strategy:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
{
usernameField: 'email',
phoneField: 'phone',
passwordField: 'password',
session: false
},
function(email, phone, password) {//cb == callback
const options = {
criteria: { email: email, phone: phone },
select: 'name username email hashed_password salt'
};
User.load(options, function(err, user) {
if (err || !user){
return res.status(400).json({
type: 'failure',
message: "User creation failed",
data: [err]
});
};
if (!user.authenticate(password)) {
return res.status(400).json({
type: 'failure',
message: "User creation failed",
data: [err]
});
};
req.login(user, {session: false}, (err) => {
if (err) {
res.send(err);
}
// generate a signed son web token with the contents of user object and return it in the response
const token = jwt.sign(user.id, 'your_jwt_secret');
return res.json({user, token});
});
});
}
));
I'm getting the following error:
TypeError: res.status is not a function
Before trying to get stuff back from passport with responde. I was trying to do it with cb(callback), as done in the tutorial, but I keep getting the same error.
Thanks in advance for any help!
There are a few issues with how you are implementing Passport's local strategy that are causing problems.
You are trying to use two fields as the username when Passport's local startegy only accepts one. (see: http://www.passportjs.org/packages/passport-local/)
function(username, password, done){}
If you want to use both as a username, you might want to consider creating your own custom strategy. This is going to be a little more in-depth, but you can start learning on the Passport Github page (https://github.com/jaredhanson/passport-strategy)
The second issue is that you are trying to get Passport to send a response in the local strategy which is not what it is intended to do. Instead, you should be passing the errors and return values to Passport's done() function, which will process them accordingly.
Here is an example of what your local strategy should look like:
passport.use(
new LocalStrategy(async (email, phone, password, done) => {
const options = {
criteria: { email, phone },
select: 'name username email hashed_password salt',
};
try {
const user = await User.load(options);
/**
* If null is returned meaning there was no user found, send the done call
* with the false flag. This tells passport to redirect to the failure URL.
*/
if (!user) {
return done(null, false);
}
/**
* If the user's password is incorrect, also return the done function with the false
* flag. This tells passport to redirect to the failure URL.
*/
if (!user.authenticate(password)) {
return done(null, false);
}
/**
* If a user is found and their password is verified, send the user object to
* the done function. This will tell Passport to call the next middelware attaching
* the user object.
*/
return done(null, user);
} catch (err) {
/**
* If there is an error with the DB call, return generic message
* for security purposes.
*/
return done('There was an internal server error.');
}
})
);
and an example of what your load function should look like:
load: options => {
return new Promise(async (resolve, reject) => {
options.select = options.select || 'email phone';
try {
const user = await this.findOne(options.criteria)
.select(options.select)
.exec();
resolve(user);
} catch (err) {
reject(err);
}
});
};
As a general best practice, I changed your callbacks to the newer method of promises (https://developers.google.com/web/fundamentals/primers/promises).
This should work in the way you are intending to use Passport.
I have created a form in angular, On click of submit button I am hitting a post request with Content-Type header with value application/x-www-form-urlencoded.
onSubmit(user:User) {
let headers = new Headers();
// to send data as form data as passport library want it as a form data
headers.append('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post('/login', user, { headers: headers }).subscribe((data) => {
console.log("Data : ", data);
});
}
and the model user is of type
// Class to bind user data
class User {
constructor(private name: string, private password: string) {
}
}
Here is the server side code
let userList: User[] = [new User(1, "Sunil"), new User(2, "Sukhi")];
let app = express();
// passport library
let passport = require('passport');
let LocalStrategy = require('passport-local').Strategy;
// middlewares
app.use(express.static("public"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ resave: false, saveUninitialized: true, secret: "secretKey123!!" }));
// passport middleware invoked on every request to ensure session contains passport.user object
app.use(passport.initialize());
// load seriliazed session user object to req.user
app.use(passport.session());
// Only during the authentication to specify what user information should be stored in the session.
passport.serializeUser(function (user, done) {
console.log("Serializer : ", user)
done(null, user.userId);
});
// Invoked on every request by passport.session
passport.deserializeUser(function (userId, done) {
let user = userList.filter(user => userId === user.userId);
console.log("D-serializer : ", user);
// only pass if user exist in the session
if (user.length) {
done(null, user[0]);
}
});
// passport strategy : Only invoked on the route which uses the passport.authenticate middleware.
passport.use(new LocalStrategy({
usernameField: 'name',
passwordField: 'password'
},
function (username, password, done) {
console.log("Strategy : Authenticating if user is valid :", username)
let user = userList.filter(user => username === user.userName);
console.log("Valid user : ",user)
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
return done(null, user[0]);
}
));
app.post('/login', passport.authenticate('local', {
successRedirect: '/done',
failureRedirect: '/login'
}));
app.get('/done', function (req, res) {
console.log("Done")
res.send(req.session)
})
app.get('/login', function (req, res) {
console.log("login")
res.send("login")
})
but every time it is returning login
In my case, the issue was with the request body. I was sending the user object directly so this was not working. To solve this I have created the body object using URLSearchParams.
let body = new URLSearchParams();
body.append('name', this.user.name);
body.append('password', this.user.password);
Here is the onSubmit method
onSubmit() {
let body = new URLSearchParams();
body.append('name', this.user.name);
body.append('password', this.user.password);
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
let options = new RequestOptions({headers});
this.http.post("/services/login", body.toString(), options).subscribe((response)=>{
let arrUrl = response.url.split("/");
let url = "/" + arrUrl[arrUrl.length-1];
this.router.navigate([url]);
});
}
Second, from the login form, I was not passing the value for password field. So the local strategy's callback(with username and password) was not getting called.
If you don't pass any of the fields then the callback method will not be called and will redirect to the failureRedirect URL.
I created a custom authorizer for API Gateway so that i can pass the Facebook token and it will authenticate it using Cognito's Federated identity.
My problem is that the fb token seems to expire so I keep getting 403 errors. I am wondering if my approach is correct. Should I pass the Facebook token as part of the request header to API gateway on every REST API call or so I pass AWS identity id instead. Any feedback is appreciated. Thank you.
var AWS = require('aws-sdk');
var cognitoidentity = new AWS.CognitoIdentity();
exports.handler = (event, context, callback) => {
var params = {
IdentityPoolId: 'us-west-2:xxxxxxxxxxxxxxxxx’, /* required */
AccountId: ‘xxxxxxxxxxxxxxxxx,
Logins: {
'graph.facebook.com': event.authorizationToken //Token given by Facebook
}
};
console.log(event.methodArn);
cognitoidentity.getId(params, function(err, data) {
if (err) {
console.log(err);
callback(null, generatePolicy('user', 'Deny', event.methodArn));
}
else{
console.log("success");
callback(null, generatePolicy('user', 'Allow', event.methodArn));
}
});
};
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17'; // default version
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke'; // default action
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
}
I have app.js as below
var express = require('express'),
path = require('path'),
favicon = require('serve-favicon'),
logger = require('morgan'),
cookieParser = require('cookie-parser'),
bodyparser = require('body-parser'),
db = require('./model/db');
var app=express();
var dbConfig = require('./db.js');
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.createConnection(dbConfig.url);
var passport = require('passport');
app.use(bodyparser.urlencoded({
extended:true
}));
var expressSession = require('express-session');
var flash = require('connect-flash');
app.use(expressSession({
secret: 'crackalackin',
resave: true,
saveUninitialized: true,
cookie : { secure : false, maxAge : (4 * 60 * 60 * 1000) }, // 4 hours
}));
app.use(passport.initialize());
app.use(passport.session());
// Using the flash middleware provided by connect-flash to store messages in session
// and displaying in templates
var flash = require('connect-flash');
app.use(flash());
// Initialize Passport
var initPassport = require('./passport/init');
initPassport(passport);
var routes = require('./routes/index')(passport);
app.use('/', routes);
/// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
app.listen(6061,function(){
console.log("started on port 6061");
});
/passport/init.js
var login = require('./login');
var signup = require('./signup');
var User = require('../models/user');
module.exports = function (passport) {
// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function (user, done) {
done(null, user._id);
});
passport.deserializeUser(function (id, done) {
try {
User.getById(id, function (err, user) {
done(err, user);
});
} catch (ex) {
}
});
// Setting up Passport Strategies for Login and SignUp/Registration
login(passport);
signup(passport);
}
when I write console.log(req.session) in /passport/login.js at validation function.
I get output like
Session {
cookie:
{ path: '/',
_expires: 2017-02-01T14:10:38.523Z,
originalMaxAge: 14400000,
httpOnly: true,
secure: false } }
I miss the
passport: {}
so I can login but can not generate passport session.
So isAuthenticated function say "login plase".
how can I manage session here.