Add updateMe route to Strapi 4.0's users-permissons plugin
Introduction
If you are just interested in the solution, feel free to skip the background of my post and jump straight to the solution part.
A few months ago I started setting up a web application for a friend who is currently starting his own company. The application is rather simple, a (hopefully) nice looking frontend should display the content managed in some sort of CMS and users should be able to create accounts and manage their profiles. As I wanted give Strapi a try for quite a while I felt this was the perfect project for it.
So far I am pretty happy with my decision as I am becoming more and more familiar with Strapi. However this weekend I started working on the user management I faced the first frustrating hill in my Strapi journey. Strapi includes a default plugin called users-permissions that easily enables JWT-based user management out of the box. Our application allows users to create a profile with just some basic information and they can add more details later at any point. Strapi, however, currently offers by default no way of making sure a user can only update it’s own profile. There is a GET api/users/me endpoint allowing us to get the User object for the currently logged in user identified by a JWT Token, however there is no PUT api/users/me endpoint that lets us update the User object in the same way. There also is a PUT api/users/:id endpoint allowing us to update a users information if we know it’s id. However, if we enable access to this endpoint for all authenticated users by default everyone can update anyone’s information.
A lot of users already had the same issues and quite a few resources on how to solve this are floating around in the web, but with Strapi 4.0 a lot changed regarding plugins and extensions and I had to consult multiple sources to finally find a working solution. I tried adding a policy first and this is definetly also a viable solution, however in the end I prefered choosing the User object that needs to be updated based on the JWT Token the logged in user provides when calling the API, just like the already existing GET api/users/me endpoint already does.
As this search had me quite frustrated I decided to write my first Medium post in hopes that I can shorten your search by sharing my approach here.
Solution
In the end the solution was rather simple, so I will post the full code here first and discuss the different parts afterwards. In ./src/extensions/users-permissions/ create the file strapi-server.js with the following content:
module.exports = (plugin) => {
plugin.controllers.user.updateMe = (ctx) => {
ctx.params.id = ctx.state.user.id;
return plugin.controllers.user.update(ctx);
}
plugin.routes['content-api'].routes.push({
method: 'PUT',
path: '/users/me',
handler: 'user.updateMe'
});
return plugin;
}This creates a new endpoint PUT /api/users-permissions/users/me that takes hte user id encoded in the user’s JWT token and calls the already existing update logic to update the according User object. In the admin panel you will find a new checkbox for enabling user’s to access the newly created endpoint
Explanation
As explained here a Strapi plugin’s interface can be extended in two ways. To extend a plugin’s backend functionality we can add a strapi-server.js file in the according extensions folder (./src/extensions/<plugin-name>) and export a function like this:
module.exports = (plugin) => {
//add extensions here
return plugin;
}Here we can update different aspects of Strapi’s backend logic as described here. We are interested in only two things though. We want to have a new route that can execute the logic described in a new controller.
First let’s look a the controller’s code:
plugin.controllers.user.updateMe = (ctx) => {
ctx.params.id = ctx.state.user.id;
return plugin.controllers.user.update(ctx);
}The new controller shall be part of the already existing controller group called user and we want to name it updateMe. A controller takes a context object from where we want to extract the user’s id, which we can do with ctx.state.user.id . As there already is a default controller that can update a user identified by it’s id, I figured reusing this would be the best. By passing the function the already existing context object via plugin.controllers.user.update(ctx) we can make sure that the request body and all headers are transfered correctly. However we need to make one small change, as we want to take the user’s id as the selected user id for the update. That’s why we update the context object like this ctx.params.id = ctx.state.user.id .
Lastly let’s look at the code for adding the route:
plugin.routes['content-api'].routes.push({
method: 'PUT',
path: '/users/me',
handler: 'user.updateMe'
});This part is rather straight forward. The users-permission plugin defines it’s routes in a package named content-api so we add our new route also there by calling plugin.routes['content-api'].routes.push(...); . In the config object for the new route we define that we want a PUT endpoint on the path /users/me (note that /api/users-permissions/ is added by default as the route is part of the users-permissions plugin) and that it should be handled by our newly created controller user.updateMe.
And that’s it. For me this currently works like a charm. I hope I was able to help you with your current problem or gave you some inspiration for your own solution.
