Reputation: 410
So I am doing a role-based access application, and I embedded the roles of users inside the JWT. When I decode the token, I get the id, role...
. Now I have a function that checks for the token from the x-auth-token
header. This is what the function looks like.
export const decodeToken = (controller) => {
return wrapAsync(async (httpRequest) => {
const token = httpRequest.headers['x-auth-token']
if (!token) {
throw new UnauthorizedError('No token, authorization denied.')
}
const decoded = jwt.verify(token, process.env.JWT_SECRET)
httpRequest.user = decoded
return controller(httpRequest)
})
}
I also have a function that checks for the role too which is similar to the decodeToken
function, this is what the roleCheck
function looks like.
export function roleCheck(controller) {
return wrapAsync(async (httpRequest) => {
const token = httpRequest.headers['x-auth-token']
const decoded = jwt.verify(token, process.env.JWT_SECRET)
if (decoded.role !== 'admin') {
throw new UnauthorizedError(
'You do not have the authorization to perform this action'
)
}
return controller(httpRequest)
})
}
Now, there is quite some code duplication here. One way I could have solved this was to also check for the role in the decodeToken
function, but that method will cause all routes inaccessible for users who don't have the admin
role attached to the incoming payload. Now my question is how do I pass this
const token = httpRequest.headers['x-auth-token']
const decoded = jwt.verify(token, process.env.JWT_SECRET)
from the decodeToken
function to the roleCheck
function. Also, is this a use case for function composition? I feel the code can be much cleaner than this, but I just don't know how to achieve that.
Typically, a route looks like this
export function config(router) {
router
.post('/', expressCallback(decodeToken(roleCheck(postProduct))))
.get('/', expressCallback(decodeToken(getProducts)))
...
return router
}
So the decodeToken
is called first, then the roleCheck
before the request hits the postProduct
controller. In this manner, the token is gotten from the header and decoded then the controller can act based on the role.
Any help will be appreciated, thanks.
Upvotes: 1
Views: 254
Reputation: 571
You are decoding the token twice and there is no need to do so.You are decoding the token in 'decodeToken' and attaching a new field 'user' to the request object that contains all the decoded information from the token(id,role etc). Inside the 'roleCheck' you don't need to decode the token again since you already have all the decoded information stored inside the 'user' field of the request object.All you have to do is access this information and check for the role.
decodeToken :
export const decodeToken = (controller) => {
return wrapAsync(async (httpRequest) => {
const token = httpRequest.headers['x-auth-token']
if (!token) {
throw new UnauthorizedError('No token, authorization denied.')
}
const decoded = jwt.verify(token, process.env.JWT_SECRET)
httpRequest.user = decoded //here you are storing the info in user field of httpRequest
return controller(httpRequest)
})
}
Now change the name 'roleCheck' to something like 'adminRoleCheck' that checks if the user is admin or not.
adminRoleCheck :
export function adminRoleCheck(controller) {
return wrapAsync(async (httpRequest) => {
if (httpRequest.user.role !== 'admin') {
throw new UnauthorizedError(
'You do not have the authorization to perform this action'
)
}
return controller(httpRequest)
})
}
Similarly define other functions(such as customerRoleCheck) to check for the other roles.You also do not need to worry about code duplication too much as these functions only need to be defined once in your application.
Upvotes: 1