skip to Main Content

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.

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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

    1. Check if the Authorization header is available
    2. If it does not exist just throw an exception or a response with some error
    3. Check if the token is present in the Bearer token
    4. If the token is not there just throw an exception to temnate the excecution
    5. If the 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:[])
    6. Depending on validity everything here is on check
    7. If the jwt is valid then the JWT payload is there thn we can pass it to the request object to carry it through to the other middlewares
    8. If we want to now have Athorization then we override the next parameter with a function to execute on it`s behalf

    From 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search