JWT jsonwebtoken dosen't expired in node.js - javascript

I'm using "jsonwebtoken": "^5.4.0", I'am not able to expire my token
I create the token with:
var token = jwt.sign(user, app.get('superSecret'), {
expiresInSeconds: 1
});
My token is like
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NjFhODE5MjFhZGJmYWI2MzNlZWU4ZjciLCJ1c2VybmFtZSI6ImJhcm5vIiwicGFzc3dvcmQiOiIkMmEkMTAkcnh6SHY0dFFhbkxwVDNQOEVJSzNBTzVhLjcwNUJZdmxIOVhXOHlSVmpUMi9hNEdmTFd4YU8iLCJfX3YiOjAsImNyZWF0ZWRfYXQiOiIyMDE1LTEwLTExVDE1OjM0OjQyLjg3MFoifQ.ooELWBRlxtYwFTmJDFNOLiR6-2uR_-wjjXwPnS0c5Lk
In my middleware I have this check
jwt.verify(token, app.get('superSecret'), function (err, decoded) {
if (err) {
return res.json({success: false, message: 'Failed to authenticate token.'});
} else {
console.dir(decoded);
// if everything is good, save to request for use in other routes
req.decoded = decoded;
return res.json({success: true, token: decoded});
next();
}
});
With Postman I do a Post Request Like
Post http://localhost:3000/users
with
x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NjFhODE5MjFhZGJmYWI2MzNlZWU4ZjciLCJ1c2VybmFtZSI6ImJhcm5vIiwicGFzc3dvcmQiOiIkMmEkMTAkcnh6SHY0dFFhbkxwVDNQOEVJSzNBTzVhLjcwNUJZdmxIOVhXOHlSVmpUMi9hNEdmTFd4YU8iLCJfX3YiOjAsImNyZWF0ZWRfYXQiOiIyMDE1LTEwLTExVDE1OjM0OjQyLjg3MFoifQ.ooELWBRlxtYwFTmJDFNOLiR6-2uR_-wjjXwPnS0c5Lk
And My token is always valid!
console.dir(decoded)
{
"success": true,
"token": {
"_id": "561a81921adbfab633eee8f7",
"username": "myuser",
}
}
Why my code is always valid? How can I force to invalidate this code, for example like logout?

library might not invalidate token by time itself. In your case signature might be correct, so you get decoded variable. Probably you need to check it yourself.
Here is very useful tool to check what JWT token you actually generated.
http://jwt.io/#debugger

When using jwt.sign(), the first argument provided must be an object. Try replacing your code as below.
var token = jwt.sign({user: user}, app.get('superSecret'), {
expiresIn: 1
});
Also, use expiresIn instead of expiresInSeconds as it has been deprecated.

Related

Koa & Passport Missing credentials

I have a Koa server that uses Passport to authenticate users against an Array,
and a React client. After successful login, the following requests are not authenticated as the cookie is undefined. The authenticate function's error parameter has:
{ message: 'Missing credentials' }
After browsing the site, I fixed the usual errors, calling the returned function of authenticate, adding {credentials: 'include'} to fetch etc, but still I have the same problem.
Middleware list:
router.use(cookie.default());
app.use :
koa-body, koa-session-store (also tried with koa-session), passport.initialize(), passport.session(), router.routes(), koa-static
local strategy
passport.use(new Strategy((username,password,callback)=>{
var u = users.find(u=>u.username == username);
return (u && password == 'password')? callback(null, u ):callback('user not found', false);
}));
/login authenticate
.post('/login', (ctx)=>{
console.log(ctx.request.body);
return passport.authenticate('local',(err,user,info,status)=>{
if(user) {
ctx.login(user);
ctx.body = {success: true}; // works correctly
ctx.redirect('/login-success');
} else {
ctx.redirect('/login-failure');
}
})(ctx);
});
/login-success
router.get('/login-success',async(ctx)=> {
return passport.authenticate('local',(err,user,info,status)=>{
console.log(err); // "Missing credentials"
})(ctx);
await ctx.response;
ctx.body = {success: true};
}).
Client call
let body = JSON.stringify({username: this.state.username, password: this.state.password});
let result = await fetch('http://localhost:4200/login',{method:'POST',credentials: 'include',body, headers:{'Content-Type':'application/json'}});
The fix is actually simple, but the reason is hard to find.
async middleware must either call await next() or return next() at the end.
Otherwise a 404 error is caused.
Adding await next() to the async /login-success callback, fixed the issue.
Documentation: https://github.com/koajs/koa/blob/master/docs/troubleshooting.md#my-middleware-is-not-called

Using ably.io JWT with Angular

I'm trying to use ably.io with Angular and Azure Functions using the JWT way of authenticating since it's secure, but I'm having issues with configuring the angular side of it. The use case is for a live auction site to update bids in realtime. There isn't a specific angular tutorial for this so I'm trying to piece it together. Also this code
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
never throws the alert so I don't think it's working right. I used to have it working where I wasn't using ably JWT, but had the API key on the client-side in a component like this
let api = "<api key>";
let options: Ably.Types.ClientOptions = { key: api };
let client = new Ably.Realtime(options); /* inferred type Ably.Realtime */
let channel = client.channels.get(
"auctions"
);
and I could subscribe to that channel and update auctions accordingly by their id inside ngOnInit()
channel.subscribe(message => {
const auction = this.posts.find(action => {
return action.id === message.data.auctionId;
});
if (auction) {
auction.currentBid = message.data.lastBid;
}
});
but I need to switch this logic for JWT and somehow feed that JWT token into different components as well.
Ably.io JWT tutorial reference
I put the following in my angular login service
login(email: string, password: string) {
const authData: AuthDataLogin = { email: email, password: password };
return this.http
.post<{
token: string;
expiresIn: number;
userId: string;
}>(environment.azure_function_url + "/POST-Login", authData)
.pipe(takeUntil(this.destroy)).subscribe(response => {
//JWT login token. Not Ably JWT Token
const token = response.token;
this.token = token;
if (token) {
console.log('Fetching JWT token from auth server')
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
...
}
With my azure function already configured, When I login, the browser console outputs
GET wss://realtime.ably.io/?access_token=<token was here>&format=json&heartbeats=true&v=1.1&lib=js-web-1.1.22
SO it returns my token, but
the alert never happens
I'm not sure how to grab that JWT token that's returned to the browser. I was thinking I could store it in localStorage to share between components and clear out localStorage when user logs out, but I need to be able to subscribe to response and assign the token to a variable, but I didn't see in ably javascript tutorial how to get variable assigned to JWT Token response since it's being called with this syntax.
I appreciate any help with this!
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
My azure function looks like
const checkAuth = require('../middleware/check-auth');
var jwt = require("jsonwebtoken")
var appId = '<APP ID>'
var keyId = '<key ID>'
var keySecret = '<key secret>'
var ttlSeconds = 60
var jwtPayload =
{
'x-ably-capability': JSON.stringify({ '*': ['publish', 'subscribe'] })
}
var jwtOptions =
{
expiresIn: ttlSeconds,
keyid: `${appId}.${keyId}`
}
console.log("JwtPayload");
console.log(jwtPayload);
console.log("jwtOptions");
console.log(jwtOptions);
module.exports = function (context, req) {
console.log("INSIDE ABLY AUTH")
// checkAuth(context, req);
console.log('Sucessfully connected to the server auth endpoint')
jwt.sign(jwtPayload, keySecret, jwtOptions, function (err, tokenId) {
if (err) {
console.log("ERR")
console.log(err)
console.trace()
return
}
context.res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate')
context.res.setHeader('Content-Type', 'application/json')
console.log('Sending signed JWT token back to client')
console.log(tokenId)
context.res = {
status: 200,
body: JSON.stringify(tokenId),
headers: {
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Set-Cookie",
"Access-Control-Max-Age": "86400",
"Vary": "Accept-Encoding, Origin",
"Content-Type": "application/json"
}
};
context.done();
})
}
I'd recommend if you're wanting to intercept the JWT prior to passing it to Ably (so as to verify the contents, and also use the JWT for other components), you make use of authCallback instead of authUrl. You can use a function instead of a direct URL, within which you can call the endpoint, and do anything you like with the response, prior to passing the JWT back to the Ably constructor. I've made a JavaScript example of using the authCallback for normal Token Authentication, but the same principle applies.
As to why you're not seeing the alert, it looks like you're sending an invalid JWT for what Ably is expecting, and thus you're not successfully connecting to Ably. For example, you're specifying 'expiresIn' rather than 'exp'. For a token to be considered valid, it expected certain elements in a very specific structure, see the documentation. I'd recommend for this sort of situation where you're not certain what's breaking that you make use of verbose logging, which you can enable in the connection constructor as "log": 4.

Cognito getId: NotAuthorizedException: Invalid login token. Not a valid OpenId Connect identity token

I am new to Cognito (JWT tokens & whole auth thing in general) so pardon me for asking stupid questions. I am trying to use Cognito user pools with identity pools. I logged in a user using the default URL (https://testapi123.auth.us-east-2.amazoncognito.com/login?response_type=token&client_id=<>&scope=openid&redirect_uri=https://aws.amazon.com) and got a token. I am unable to figure out how to get the identity id from this token for use in getId() API. This I want to later use to get credentials from the federated identity pool (not sure if i have that part right either).
For reference, i have my code -
var AWS = require('aws-sdk');
var cognitoidentity = new AWS.CognitoIdentity({apiVersion: '2014-06-30'});
exports.handler = (event, context, callback) => {
// TODO implement
var params = {
IdentityPoolId: 'us-east-2:<xxxxxx>', /* required */
AccountId: 'xxxxxxx',
Logins: {
'cognito-idp.us-east-2.amazonaws.com/us-east-2_xxxxx': '<**identityID???**>',
}
};
cognitoidentity.getId(params, function(err, data) {
if (err) console.log('Error3 : ' + err, err.stack); // an error occurred
else {
console.log('retval:' + JSON.stringify(data)); // successful response
var idenId = data.idenId;
var params = {
IdentityId: idenId,
CustomRoleArn: 'arn:aws:iam::xxxxxxxxx:role/cc_admin',
Logins: {
'CognitoIdentity': 'cognito-idp.us-east-2.amazonaws.com/us-east-2_xxxxxxx'
}
};
cognitoidentity.getCredentialsForIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
}
});
callback(null, 'Hello from Lambda 1');
};
The error - "2018-06-06T18:59:58.614Z ce0fc216-69bb-11e8-bbfc-4fff0d953dd4 Error3 : NotAuthorizedException: Invalid login token. Token signature invalid. NotAuthorizedException: Invalid login token. Token signature invalid."
I have tried parsing the JWT token received (with jwt.io). It shows me some details but none of them seem to be identity id to be used in the request. I have also tried using the entire token as identity id.
Really need help. Thanks.
First, you only need to pass Identity Pool Id in params for getId function to work. It will return IdentityId associated with your identity pool. Replace
var params = {
IdentityPoolId: 'us-east-2:<xxxxxx>', /* required */
AccountId: 'xxxxxxx',
Logins: {
'cognito-idp.us-east-2.amazonaws.com/us-east-2_xxxxx': '<**identityID???**>',
}
};
with
var params = {
IdentityPoolId: 'us-east-2:<xxxxxx>'
}
Second, you can't create credentials unless you have idToken i.e JWT (JSON Web Token which is returned to client after successful authentication by user pool).
It looks like this..
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
You can get this token only by signing in...Run this command on terminal after configuring aws-cli or use aws-sdk and run function InitiateAuth
aws cognito-idp admin-initiate-auth --user-pool-id ap-south-1_xxxxxxx --client-id AAAAAAAAAAAAAAAAAAAAA --auth-flow ADMIN_NO_SRP_AUTH --auth-parameters USERNAME="abc#123.com",PASSWORD="********"
Finally,Change your params function passing to getCRedentials method 'Logins' key to:
Logins: {
"IdentityId": <result from getId>
"cognito-idp.us-east-2.amazonaws.com/us-east-2_xxxxxxx": <your idToken>
}

Payload error in jsonwebtoken

I am making a web application using nodejs and angular cli
I'm using JWT to authenticate my login function . But when I process it threw this error
Error: Expected "payload" to be a plain object.
at validate (D:\Mean_Projects\meanauthapp\node_modules\jsonwebtoken\sign.js:34:11)
at validatePayload (D:\Mean_Projects\meanauthapp\node_modules\jsonwebtoken\sign.js:56:10)
at Object.module.exports [as sign] (D:\Mean_Projects\meanauthapp\node_modules\jsonwebtoken\sign.js:108:7)
at User.comparePassword (D:\Mean_Projects\meanauthapp\routes\users.js:86:27)
at bcrypt.compare (D:\Mean_Projects\meanauthapp\models\user.js:53:9)
at D:\Mean_Projects\meanauthapp\node_modules\bcryptjs\dist\bcrypt.js:297:21
at D:\Mean_Projects\meanauthapp\node_modules\bcryptjs\dist\bcrypt.js:1353:21
at Immediate.next [as _onImmediate] (D:\Mean_Projects\meanauthapp\node_modules\bcryptjs\dist\bcrypt.js:1233:21)
at runCallback (timers.js:785:20)
at tryOnImmediate (timers.js:747:5)
at processImmediate [as _immediateCallback] (timers.js:718:5)
Here my passport code
const JwtStrategy= require('passport-jwt').Strategy;
const ExtractJwt=require('passport-jwt').ExtractJwt;
const User= require('../models/user');
const config=require('../config/database');
module.exports=function(passport){
let opts={};
opts.jwtFromRequest=ExtractJwt.fromAuthHeader();
opts.secretOrKey=config.secret;
opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';
passport.use(new JwtStrategy(opts,(jwt_payload,done)=>{
console.log(jwt_payload);
User.getUserById(jwt_payload._doc._id,(err,user)=>{
if(err){
return done(err,false);
}
if(user){
return done(null,user);
}
else{
return done(null,false);
}
});
}));
}
My code for authenticate and get profile
// Authenticate
router.post('/authenticate', (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
User.getUserByUsername(username, (err, user) => {
if(err) throw err;
if(!user){
return res.json({success: false, msg: 'User not found'});
}
User.comparePassword(password, user.password, (err, isMatch) => {
if(err) throw err;
if(isMatch){
const token = jwt.sign(user, config.secret, {
expiresIn: 604800 // 1 week
});
res.json({
success: true,
token: 'JWT '+token,
user: {
id: user._id,
name: user.name,
username: user.username,
email: user.email
}
});
} else {
return res.json({success: false, msg: 'Wrong password'});
}
});
});
});
// Profile
router.get('/profile', passport.authenticate('jwt', {session:false}), (req, res, next) => {
res.json({user: req.user});
});
It fails at the line
const token = jwt.sign(user, config.secret, {
With error "Expected "payload" to be a plain object"
Your user object is initialized here:
User.getUserByUsername(username, (err, user)
Which I assume is mongoosejs object, which contains many methods and is not "serializable". You could handle this by passing a plain object, by either using .lean() from mongoose or plain toJSON method:
const token = jwt.sign(user.toJSON(), config.secret, {
expiresIn: 604800 // 1 week
});
I had this problem as well, with a returned user from mongoose, just add toJSON() or toObject() will fix the issue, but what happens if your user is not always coming from mongoose?
You will get a
user.toJson/user.ToObject is not a function
if you try to do this on a plain object.
If your user is coming from different sources and you don't know if it will be a plain object or not, you can solve it like this:
JSON.parse(JSON.stringify(user));
this is clearly mentioned in the migration doc of passport-jwt
that they have removed the ExtractJwt.fromAuthHeader() from version 2 and 3 and also to use the new method ExtractJwt.fromAuthHeaderAsBearerToken() or one of like that in place of old method. for compelte reference visit
From your log there is the issue
User.comparePassword (D:\Mean_Projects\meanauthapp\routes\users.js:86:27) at
so here four thing need to be updated in your code #every Bit
First in package.json file
Change the version to latest by using * or version no like this
by going to project directory and run the command
npm install passport-jwt --save
"dependencies": {
....
"passport-jwt": "^3.0.1"
}
or
write in the file and run the commadn
`npm install`
"dependencies": {
....
"passport-jwt": "*"
}
Second change this line of your code in authenticate method
const token = jwt.sign(user.toJSON(), config.secret, {
expiresIn: 604800 // 1 week
});
Third in the passport code change the old method
ExtractJwt.fromAuthHeader();
to new one, from the doc reference you need to use this method opts.jwtFromRequest=ExtractJwt.fromAuthHeaderWithScheme('jwt');
and fourth change this
User.getUserById(jwt_payload._id,(err,user)=>{
This solution will work on latest version's
if you still want to use this old method then
only change the version of your passport-jwt in package.json to 1.x.x (x is the nuber here )of your choise of lower version then 2, by moving to project folder and runing the command npm install
the only thing you need to check is data in the payload_jwt,it will be inside the second layer so please check the jwt_payload.
ok you are all set to go you had already handled User.getUserById(jwt_payload._doc._id,(err,user)=>{
It's very simple, if the user comes from database (mongo) then simply do user.toJSON(), if the user comes from any other source then simply do JSON.stringify(user).
if it's not comming from moongose
then use spread operator
const token = jwt.sign({ ...payload }, config.secret, {
expiresIn: 100080
});
Change
const token = jwt.sign(user, config.secret, { expiresIn: 10080 });
To
const token = jwt.sign(user.toJSON(), config.secret, { expiresIn: 10080 });
const token = jwt.sign(user, config.secret, {
expiresIn: 604800 // 1 week
});
convert this to
const token = jwt.sign(user.toJSON(), config.secret, {
expiresIn: 604800 // 1 week
});
or you need to console.log(jwt_payload); to find your ID comes under _doc or directly with jwt_payload. because this may change with the versions.

Why adding a get parameter forces middleware execution

I have got a middleware like this
// route middleware to verify a token
app.use(function(req, res, next) {
console.log(req.baseUrl);
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.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.'
});
}
});
This route http://localhost:8080/verifyAccount doesn't responds as No token provided
app.get('/verifyAccount', function (req, res) {
res.json({ message: 'Welcome to verify account!' });
});
But the following route http://localhost:8080/verifyAccount?id=123 does:
app.get('/verifyAccount/:id', function (req, res) {
res.json({ message: 'Welcome to verify account!' });
});
The middleware code is at the bottom in the code file and the get paths are upwards
What is that concept behind?
Why adding a get parameter forces middleware execution?
Just found that if I call it like this http://localhost:8080/verifyAccount/id=123, it properly returns Welcome to verify account!
Found that the issue was in the way by which the route was getting called. Thanks to Thomas Theiner for the help.
The request with query parameter ?id=123 does not match with /:id. It should be called as verifyAccount/123 instead.
Since, the route ?id=123 did not matched any of the path. Hence, was finally reaching the middleware for execution
The position determines the parameter, not the name. The name is only used inside node code to reference the parameter.
For multiple parameters, we'll have multiple slashes like verifyAccount/:id/:otherParameter which would be called using verifyAccount/123/234

Categories