Token Auth with JWTs Part 4 - Changing Passwords
If you haven't been following along in this Token Authentication series, this post won't make a whole lot of sense. You can find part 1 of the series beginning here, and each post links to the next one in the series at the end.
Allowing a user to change his/her password is a pretty important part of any application. There are, as always, many ways in which you could implement this kind of feature, but we're going to take a look at two:
- Allowing the user to change their password from their profile page,
- Allowing the user to reset their password via an email link when they've forgotten it.
The subject of this post is the first method. We can assume the user is logged in to their account and haven't forgotten their password, but simply want to change it to something new.
The steps we'll need to take are:
- Updating the server-side code
- Adding a
"/change-password"
endpoint to our authRoutes - Protecting the
"/change-password"
route with theexpress-jwt
package, since we only want to let authenticated users to change their own passwords.
- Adding a
- Updating the client-side code
- Adding a new profile page, a controller to control that page, and a link in the navbar so the user can easily access that page
- A new
.when
route in ourapp.config
- A
changePassword
method on theUserService
to call out to the new/change-password
server endpoint.
Updating the server-side code
Add a new route to authRoutes.js
We're going to create a new endpoint inside our authRoutes. In the future, you may consider moving this into a new router that deals solely with updating a user's information, but for now we should be okay putting it inside the authRoutes:
// authRoutes.js
authRoutes.post("/change-password", function (req, res) {
User.findById(req.user._id, function (err, user) {
if (err) {
res.status(500).send(err);
} else {
user.password = req.body.newPassword || user.password;
user.save(function (err, user) {
res.send({success: true, user: user.withoutPassword()});
});
}
});
});
Protect the /auth/change-password
route with express-jwt
We want to make sure that any calls to our server to the /auth/change-password
route are protected by express-jwt
. If the user isn't logged in, they shouldn't be able to change their password - at least, not until we set up the "forgot password" link in the next section.
To do this, we simply add another app.use
statement to our server.js
file:
// server.js
app.use("/api", expressJwt({secret: config.secret}));
// This is the new part. Make sure it goes above the app.use("/auth") line below
app.use("/auth/change-password", expressJwt({secret: config.secret}));
app.use("/auth", require("./routes/authRoutes"));
That should be all the changes we need on our server, so let's get started with the front-end changes!
Updating the client-side code
Add a new profile page and controller
We need a new component to our web app, so we'll create a new profile
folder inside the components folder
and create a couple new files:
// components/profile/profile.html
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1>{{ userService.currentUser.name }}</h1>
<img src="http://placekitten.com/g/100/100" alt="Kittens" class="img-responsive">
<hr>
<div class="row">
<div class="col-md-4">
<h3>Change Password</h3>
<form>
<div class="form-group">
<label for="newPassword">New Password</label>
<input type="password" id="newPassword" class="form-control" ng-model="passwords.newPassword">
</div>
<div class="form-group">
<label for="newPasswordRepeat">Repeat New Password</label>
<input type="password" id="newPasswordRepeat" class="form-control" ng-model="passwords.newPasswordRepeat">
</div>
<button class="btn btn-primary" ng-click="changePassword(passwords)">Change Password</button>
</form>
</div>
</div>
</div>
</div>
And here's an associated ProfileController:
var app = angular.module("TodoApp");
app.controller("ProfileController", ["$scope", "UserService", function ($scope, UserService) {
$scope.userService = UserService;
$scope.changePassword = function (passwords) {
if (passwords.newPassword === passwords.newPasswordRepeat) {
UserService.changePassword(passwords.newPassword).then(function(response) {
$scope.passwords = {};
})
} else {
alert("The two passwords didn't match");
}
}
}]);
Link the new profile.js
in the index.html:
<script src="components/profile/profile.js"></script>
Add a new .when
block to our app's config:
.when("/profile", {
templateUrl: "components/profile/profile.html",
controller: "ProfileController"
})
Add another li
to our navbar directive's HTML:
This way a logged-in user can see their profile page
<!-- navbar.html -->
<li ng-show="userService.isAuthenticated()"><a href="#/profile">Profile</a></li>
Add to the UserService
Our controller has a function to change the password that calls UserService.changePassword()
, so we need to include that method in our UserService:
// auth.js
...
this.changePassword = function (newPassword) {
console.log(newPassword);
return $http.post("/auth/change-password", {newPassword: newPassword}).then(function (response) {
alert("Password Changed Successfully!");
return response.data;
}, function (response) {
alert("Problem with the server");
});
};
...
Conclusion
The above code should give your app a new profile page with the ability to let the logged-in user change their password. It's pretty straightforward, nothing added in this feature should be strikingly new to you. Make sure you walk through the code carefully and understand how each piece interacts with another.
Next we're going to tackle a major update to our Todo application - allowing a user who has forgotten their password to reset it by receiving an email with a special link to set a new password. Token Auth with JWTs Part 5 - Forgotten Password.