I have function that just send data to database (my posts). I use private and public keys to sign and verify tokens. I can send this token in header from front-end to back-end, but has problem with verifying it. Here is how this flow looks like:
Front-end
router.post(`/p-p`, async (req, res) => {
try {
const data = await api.post(`/post-post`, req.body, {
headers: {
Authorization: 'Bearer ' + req.body.token
}
})
res.json(data.data)
} catch (e) {
res.status(e.response.status).json(e.response.data)
}
})
Back-end
router.post(
"/post-post",
auth,
wrapAsync(generalController.postPost)
)
Middleware auth
const jwtService = require('./../services/jwtService')
module.exports = async(req, res, next) => {
if (req.headers.authorization) {
const user = await jwtService.getUser(req.headers.authorization.split(' ')[1])
if (user) {
next();
} else {
res.status(401).json({
error: 'Unauthorized'
})
}
} else {
res.status(401).json({
error: 'Unauthorized'
})
}
}
And JWT service
const jwt = require('jsonwebtoken');
const fs = require("fs");
const path = require("path");
const pathToKeys = path.resolve(__dirname, "../../keys");
module.exports = {
sign(payload) {
const cert = fs.readFileSync(`${pathToKeys}/private.pem`);
return jwt.sign(
payload,
{
key: cert,
passphrase: process.env.JWT_PASSPHRASE
},
{
algorithm: "RS256",
expiresIn: "30m"
}
)
},
getUserPromise(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, fs.readFileSync(`${pathToKeys}/public.pem`), (err, decoded) => {
if(!err) {
return resolve(decoded);
} else {
return reject(err);
}
})
})
},
async getUser (token) {
return await this.getUserPromise(token)
}
}
The problem starts after getUserPromise function. This function can get a token, but can't verify it and I have this problem:
UnhandledPromiseRejectionWarning: JsonWebTokenError: jwt malformed
Actually, I have no idea where problem is. I generated key pair, and sign function can sing and return token, which looks like this: 351e38a4bbc517b1c81e180479a221d404c724107988852c7768d813dd0510e6183306b1d837091b2cddaa07f2427b7a
So, what's the problem?
I have found the solution of this problem and it feels shame. In JWT service pay attention to this string:
algorithm: "RS256"
As you can see I use RS256, but I generated certificates in other format, so, because of this I got that error.
So, if you use RSA certificates, pay attention to algorithm!
EDIT:
Here is how you can generate pair for RS256:
Private
openssl genrsa -out private.pem -aes256 4096
Public from private
openssl rsa -in private.pem -pubout > public.pem
Related
I am trying to intercept a request from apollo client to apollo server and return either a different typeDef field or an undefined one without the client resulting in an error.
This is to check the users refresh token if there access token is expired, and send a new access token instead of the nomral query they were expecting.
Here is my code:
Server:
const server = new ApolloServer({
typeDefs,
resolvers,
cors: {
origin: "http://localhost:3000",
credentials: true,
},
context: async ({ req, res }) => {
let isAuthenticated = false;
// get the user token from the headers
const authorization = req.get("Authorization");
if (authorization) {
let token = req.headers.authorization.replace(/\(/g, "");
let verifysomething = jwt.verify(
token,
process.env.JWT_SECRET,
(err, decoded) => {
console.log("access decoded");
console.log(decoded);
if (err) {
return err;
}
return decoded;
}
);
if (verifysomething.message == "jwt expired") {
let refreshToken = req.headers.cookie.replace(/^[^=]+=/, "");
let verifyRefresh = jwt.verify(
refreshToken,
process.env.JWT_SECRET,
(err, decoded) => {
if (err) return err;
const accessToken = jwt.sign(
{ userId: decoded.userId },
process.env.JWT_SECRET,
{
expiresIn: "20s",
}
);
isAuthenticated = "newAccess";
return accessToken;
}
);
//end return if now access token but has refresh token
return { isAuthenticated, res, verifyRefresh };
}
let user = await User.findOne({ _id: verifysomething.userId });
if (user) {
isAuthenticated = true;
return {
isAuthenticated,
user,
};
}
}
return { isAuthenticated, res };
},
});
Resolvers:
const resolvers = {
Query: {
getCartItems: authenticate(
async (_, { id }) =>
await cartItem.find({ user: id }).populate("user").populate("item")
),
}}
Authenticate:
function authenticate(resolver) {
return function (root, args, context, info) {
if (context.isAuthenticated == true) {
return resolver(root, args, context, info);
}
else if ((isAuthenticated = "newAccess")) {
return context.verifyRefresh;
}
throw new Error(`Access Denied!`);
};
}
To clarify, the issue is that a request going to getCartItems will be expecting a retun like:
type CartItemPayload {
item: Item
user: User
}
but if their access token is expired I would like to just send them a new one first instead.
Now one way I thought to solve this was by adding a new mutation query for refresh token and on each request from the client I can decode the access token jwt to see if it is expired and if so then I make a request to the refreshToken mutation instead of the getCartItems query for example, and that way I would be able to get the expected typeDef return and easily control the data I am receiving.
The problem I see with that is this just feels like a problem that is supposed to be solved on the serverside. I am new to apollo and graphql and am not using an express server for middleware nor am I using redux for middleware on the client side and not sure if what I am trying to achieve is possible this way.
I am open to any solutions or suggestions for this problem, thanks.
I want to protect my routes by adding a middleware 'checkAuth'.
This middleware checks the validity of a jwt token.
I'm using Express router.
But I don't understand how to do that.
My checkAuth middleware :
module.exports = (req, res, next) => {
let token = req.headers.authorization.split(" ")[1];
try {
jwt.verify(token)
console.log("ok")
}catch (e) {
res.status(403)
}
next();
}
Thank you !
Assuming you are using jsonwebtoken, you are missing the "secret" string.
According the documentation that's how you should do.
when creating token:
var jwt = require('jsonwebtoken');
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
You could also pass expiration time:
jwt.sign({
data: 'foobar'
}, 'secret', { expiresIn: 60 * 60 });
for validating:
There a couple of ways you could do it.
But you should need the same secret string to validate that you used for signing in.
Also you need to assign a variable to jwt.verify or call it with a callback in order to access the decoded data, such as user Id and so on.
// verify a token symmetric - synchronous
var decoded = jwt.verify(token, 'shhhhh');
console.log(decoded.foo) // bar
// verify a token symmetric
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
// invalid token - synchronous
try {
var decoded = jwt.verify(token, 'wrong-secret');
} catch(err) {
// err
}
// invalid token
jwt.verify(token, 'wrong-secret', function(err, decoded) {
// err
// decoded undefined
});
Create a new function called "verifyToken"
I suggest to promisfy it. So you can use it in an async function in combination with await
function verifyToken(token){
return new Promise((res, err) => {
jwt.verify(token, "secret key", (err) => {
if (err) rej(err)
res(true)
})
})
}
Its promise based. Now you just pass your token to the function it resolves to either true or false:
module.exports = async (req, res, next) => {
let token = req.headers.authorization.split(" ")[1];
try {
await verifyToken(token);
console.log("ok")
}catch (e) {
res.status(403)
}
next();
}
I have two different API routes, with several endpoints embedded in them. One of them is protected and is only accessible if you send a JWT token in the request. The other one, of course, is for Auth that allows a user to generate a token and then append it to every request. Assuming the relevant imports and that my Auth code is in Auth.jsand that the Add Candidate is in Candidate.js:
Auth.js
router.post('/login', (req, res) => {
let { email, password } = req.body;
db.collection('users').findOne({ email: email }, { projection: { email: 1, password: 1, accessGranted: 1 } }, (err, userData) => {
console.log(userData);
if (!err) {
let passwordCheck = bcrypt.compareSync(password, userData.password);
if (passwordCheck) {
if (userData.accessGranted) {
const payload = {
email: userData.email,
id: userData._id
}
let token = jwt.sign(payload, tokenSecret);
res.status(200)
.send({ token, email: userData.email });
} else {
//.. Other Code
}
} else {
// Other code
}
} else {
// Other code
}
})
})
Now, I have added a middleware in the other file as such:
Candidate.js
router.use((req, res, next) => {
const token = req.body.token;
if (token) {
jwt.verify(token, tokenSecret, function (err, decoded) {
if (!err) {
req.decoded = decoded;
next();
} else {
// Other Code
}
})
} else {
// Other Code
}
}
);
Error: jwt complains that the token I have supplied is invalid.
For prototyping, I have saved the token in a JS const variable and I have double-checked that the tokenSecret in both the files is the same. What's wrong?
I'm authenticating calls to my express API using passport. I have a pretty standard setup:
/* Passport Setup */
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
secretOrKey: config.auth.passport.key,
}
passport.use(
'jwt',
new JWT.Strategy(jwtOptions, (payload, done) => {
console.log('Using JWT Strategy')
User.findOne({ email: payload.email }, (err, user) => {
if (err) {
return done(err, false)
}
if (user) {
done(null, user)
} else {
done(null, false)
}
})
}),
)
/* Middleware */
const checkToken = passport.authenticate('jwt', { session: false })
const logAuthInfo = (req, res, next) => {
console.log(req.headers)
console.log(req.user)
}
/* Routes */
app.use(passport.initialize())
app.use('/graphql', checkToken, logAuthInfo, graphqlHTTP(graphQLConfig))
// other REST routes, including login
My login route returns a JWT, and when a request is made to /graphql with this token, everything works. But, an unauthenticated request (with no token) returns a 401. What I'd like to do differently is use the checkToken middleware on all requests, assigning req.user to either the authenticated user data or false. I'd then handle any authorization elsewhere.
When I make a request without a token, I don't see 'Using JWT Strategy' log to the console, so that middleware isn't even running.
Any ideas?
Ok, I figured this out shortly after posting this. For anyone coming here with the same question -- the solution is not using passport-jwt to achieve this, but rather the underlying jsonwebtoken.
My working middleware now looks like:
const jwt = require('jsonwebtoken')
const PassportJwt = require('passport-jwt')
const getUserFromToken = (req, res, next) => {
const token = PassportJwt.ExtractJwt.fromAuthHeaderWithScheme('Bearer')(req)
jwt.verify(token, jwtSecret, (err, decoded) => {
if (err) {
req.user = false
next()
return
}
req.user = decoded
next()
})
}
app.use(getUserFromToken)
app.use('/graphql', graphqlHTTP(graphQLConfig))
// Elsewhere, in my GraphQL resolvers
const userQuery = (obj, args, request, info) => {
// ^^^^^^^
// I've also seen this parameter referred to as 'context'
console.log(request.user) // either 'false' or the serialized user data
if (req.user) {
// do things that this user is allowed to do...
} else {
// user is not logged in, do some limited things..
}
}
In every request I send token, and check it in express middleware
app.use(async (req, res, next) => {
const authorization = req.headers.authorization;
let token = null;
let user;
if (authorization) {
try {
token = jwt.verify(authorization, config.secret);
} catch (e) {
// dont work
throw new GraphQLError({ message: 'token damaged' });
}
if (token) {
const { _id } = token;
user = await User.findOne({ _id });
}
if (user) {
req.user = user;
}
}
next();
});
Token can be damaged, and I do the check:
try {
token = jwt.verify(authorization, config.secret);
} catch (e) {
throw new GraphQLError({ message: 'token damaged' });
}
So I need to send to client application Express Error, but it dont work, as expected,
are there any options to create graphql middlewares which take request arguments before calling every resolver? Now if I want throw error of damaged token I need write check in every resolver?
You can simply respond and return, without calling the next middleware:
try {
token = jwt.verify(authorization, config.secret);
} catch (e) {
res.statusCode = 401;
return res.end('{"errors": [{"message": "token damaged"}]}');
}