Reputation: 614
Learning about the concept of microservices in Nodejs, I have set up two microservices auth and users, both standalone and running on different ports.
The auth service handles user creation and log-in users using a username and password. This works as expected. I've used jwt to generate the tokens. I can easily create a user, create a session token and verify its validity.
My second service, users, I intend to use to show greetings to users and fetch a user's detail. I need to use the auth service to know when a user is logged in in this setting.
However, with my current workings, when I try to go to an endpoint, /users/:id/sayhello
with a valid user id and a valid token passed in the headers, I get the following errors:
TypeError: Cannot read properties of undefined (reading 'id') at /path/auth/verifyToken.js:23:21
.
And then this; from jwt:
{
"name": "JsonWebTokenError",
"message": "secret or public key must be provided"
}
Let's look at my setup now.
Here's my verifyToken.js file from the auth service:
const verifyToken = (req, res, next)=>{
const authHeader = req.headers.token
// split the header, and get the token "Bearer token"
const token = authHeader.split(" ")[1];
if (authHeader) {
jwt.verify(token, process.env.JWT_SEC, (err, user)=>{
if (err) res.status(403).json(err);
req.user = user
next();
})
} else {
return res.status(401).json("You are not authenticated")
}
}
const verifyTokenAndAuthorization = (req, res, next) =>{
verifyToken(req, res, ()=>{
if(req.user.id === req.params.id){ // error traced back to this line
next();
}else{
res.status(403).json("Permission denied!");
}
})
}
From my users service, here's the code that uses the auth service to know when the user is logged in then say hello.
app.get("/users/:id/sayhello", verifyTokenAndAuthorization, async (req, res) => {
try {
const user = await User.findById(req.params.id);
console.log(req.params.id) // undefined
res.status(200).json(`Hello ${user.username}`);
} catch (error) {
res.status(500).json(error);
}
});
I've with no success sought any leads from similar posts like A,B and C
I'm not sure of what's not right. I'll appreciate possible suggestions and leads towards a fix.
Upvotes: 2
Views: 971
Reputation: 743
I think in the Headers
convention of using Authorization
or authorization
key will not dissappoint as its the most preferred way of doing this have something like
I have done a rolebased approach in tackling this so check the implementation as of the question rolebased structure. What to check for
Authorization
header is availabletoken
is present in the Bearer token
AuthHeader
and token
are present then now you can be certain that you have the token, thus you can just return the jwt.verify(...args:[])
Athorization
then we override the next
parameter with a function to execute on it`s behalfFrom here now you can check on the user Roles and return next based on what permissions they have.
import RoleModel from "../models/RoleModel"
import UserModel from "../models/UserModel"
class AuthMiddleware {
constructor(role:typeof Model, user:typeof Model) {
this.role=role
this.user=user
}
verifyJwt = async (req, res, next) => {
try {
const AuthHeader = req.headers["authorization"]
if (!AuthHeader) {
return res.status(401).json("Please provide an auth token")
}
const token = AuthHeader.split(" ")[1]
if (!token) {
return res.status(401).json("Please provide an auth token")
}
return jwt.verify(token, SECRET_KEY, async (error, payload) => {
if (error) {
return res.status(401).redirect("/auth/login")
}
const decodedPayload = payload as JWTPayloadType
req.user = decodedPayload
return next()
})
} catch (error) {
return next(error)
}
}
loginRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.USER)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
adminRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.ADMIN)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
}
export default new AuthMiddleware(RoleModel, UserModel)
Applying this to a middleware
import auth from "../middlewares/AuthMiddleware"
/**
* ************* UPDATE USER PROFILE ********
*/
router
.route("/update/profile/:id")
.put(
auth.loginRequired,
imageUpload.single("profile"),
uController.updateUserDetails,
uMiddleware.uploadProfilePic,
)
Assuming you supply the middlewares to the given route its easy to abstract away the verify jwt
and have a login_required
based on the roles you want achieved.
Full implementation of this I have on this Github repo Github link
Upvotes: 1
Reputation: 26
console.log(process.env.JWT_SEC)
The authentication process got failed, so the user property was unset on the req object, so req.user
is null
.
Ensure the integrity of your inputs.
Upvotes: 1