Rails API + JWT auth + VueJS SPA: Part 3, Passwords and Tokens management

In two previous articles (Part 1, Part 2) we’ve built a secure todos application with an ability to manage todos, a basic admin panel and a support of 3 different types of user roles.

In this part we‘ll add a forgot my password feature and an ability to edit user roles via the admin panel.

Acceptance criteria:
 — User should be able to restore their password via theforgot my password feature. Once User submits their email, they should receive a secure link via mail with reset password instructions.
 — On the password reset User should be logged out from all devices. User will have to enter their new password to log back in.
 — Admin should have an ability to edit all user roles except for their own (poka-yoke).
 — As User role is stored in the access token’s payload, once user’s role is edited — access token must be flushed, so the user will have to make a new refresh request in order to receive the access token with an updated payload.

The Backend

As we’re building an API-first application let’s start with the backend as usual. I’d propose to use a classic approach for password resets with unique password reset tokens which are used for generating reset password links.
1. Generate a rails migration.

$  rails g migration add_reset_password_fields

The migration itself:

2. Now we can add reset password token generation methods to the User model. After the password is reset we should clean up the tokens, so the same token couldn’t be used twice.

Now we’re ready to build the password resets controller. The desired flow is as follows: User submits their email (first endpoint), then gets a secure link which leads them to enter-new-password page (second endpoint), and then User enters the data and hits submit (third endpoint). So, we’d need to implement 3 actions within the controller.

3. Implement POST /password_resets endpoint. This endpoint sends mails so we’ll need to build a mailer class as well.

Password reset link goes within the mailer template.

The action itself.

Note, that even if a user is not found we should return a successful response anyway so an attacker will not be able to check whether certain email addresses are registered in the system.

4. Implement GET /reset_passwords/:token/edit . The action itself verifies if a specific reset password token is valid.

Custom exception is added as well. Let’s add a handler for this type of exceptions to the application controller.

5. Implement PATCH /reset_passwords/:token

Right after the password reset we clean up the tokens in the update action.
The routes.

And specs to ensure it works.

All of it implements the first AC on the backend. Now we can implement the second one.
6. An attentive reader might remember that the session in the application is represented with 2 tokens — access and refresh. They both are stored in redis (partly, for access token only its UID is persisted), so obviously, to flush the session we must delete the tokens from the redis. But first, we should link a user to their sessions to know exactly which sessions to flush. To be able to do that we can use namespaces which can group the sessions by their user (or any other common attributes).
Firstly, let’s add a namespace with user ID to all places in the app where we are operating over sessions, specifically signin , signup and refresh controllers.

namespace: "user_#{user.id}" attribute is added to the session declaration.

7. With this being done we can flush all sessions which share a common namespace.

8. Let’s move on to the next AC and allow Admins to edit user roles. We should keep in mind that while both Admins and Managers are allowed to view users, only Admins should have a permission to edit them (that’s controlled by the allowed_aud method). Also there’s a check in update action validating that Admins shouldn’t be able to modify their own role.
To fulfill the fourth AC we’re using flush_namespaced_access_tokens, it will keep the refresh token and remove the access only, which makes user’s locally stored access tokens invalid and user will have to perform a new refresh request.

Here‘re the specs.

API is ready and we can start to work on the frontend.

The Frontend

Foreword: JS code in this article is simple and straightforward, there‘re endless possibilities of refactoring (removing code-duplicates, using store instead of making API requests on each page load, etc), but all those improvements will lead me to a material for a separate article and I’m too lazy for this, so here goes the most naive JS implementation possible.

1. First, let’s build ForgotPassword component and add navigation links.

Update routes.

And add router links to Signin/Signup components.

Here’s the view.

(bottom sign up is centered, it only SEEMS crooked BECAUSE OF THE FONT)

2. ResetPassword.vue component — the one to which leads the link from the reset password email. The component on load sends a GET request to the reset passwords endpoint to verify that the token from URL is correct.

Routes.

Visualisation (all views are pretty similar, but still)

3. Now, when JS client users are able to reset their passwords, let’s add the ability to edit roles. We’ll create a separate Edit components for users in admin space. It also prevents non-admins from accessing users edit page and prohibits admins to modify their own roles.
To implement this check let’s add currentUserId as a getter to the Vue store.

The edit component contains a label with the selected user’s email and a select box with available roles list.

Routes.

The view.

4. We have the component, but there’s no way to navigate to it through the app yet. Let’s add links to the users edit view visible only for Admins.

The view (all links are available for Admin, except for the link to their own profile)

5. And the last, but not least. In this JS client we’re storing current user’s info in the local storage. So even in case access token is reseted after refresh on the server and the app automatically requests a new access token — it still doesn’t update user’s info within the store. Let’s fix that and request user info after refresh to surely be up to date with new user’s roles.

And that’s pretty much it, now the AC are met and we are set!


The application code can be found on GitHub.
Thanks for reading! It was fun making this series of articles. 
If you have any questions or feedback please feel free to reach me on twitter. Cheers!