This is a quick guide designed to help newbie developers to build VueJS SPA with authenticated API calls.

We’ll start with building an API-first REST-ful Rails backend. API-first means that the same API endpoints can be used by different Web/JS clients, mobile applications, 3rd party APIs, and ideally all of them should use a unified auth flow and JWT is a good fit for this goal. While in this article we’ll consider the creation of a single VueJS client, the API itself can be easily adapted to be reused by other API-consumers.

Tools for the backend:

Rails 5.2.0,

Ruby 2.4.4,

gem bcrypt 1.3.12,

gem jwt_sessions 2.1.0,

gem redis 4.0.1,

gem rack-cors 1.0.2

We are not tied up with this exact versions, I’m simply listing the ones I have installed on my local environment. Now, let’s build the classic todo app.

The Backend

Create a rails app rails new silver-octo-invention --api -T . The fancy project name is auto-generated by GitHub. -T option excludes Minitest, the default testing framework. We’ll be using RSpec . Adjust Gemfile to look somewhat like this

3. Run bundle install

4. Run rails generate rspec:install . This command generates

.rspec

spec/spec_helper.rb

spec/rails_helper.rb

5. Now, let’s create the User model. We’ll start with the minimum required model fields rails g model user email:string password_digest:string

6. Add null: false settings to the migration strings.

7. Run rails db:migrate

8. Add has_secure_password method call to the User model.

9. Now, let’s create todos. Run rails g model todo title:string user:references && rails db:migrate

10. Let’s build the controllers. First, we’ll need to include JWTSessions::RailsAuthorization into ApplicationController . The module provides authorization actions which will protect secure endpoints.

11. We’ll need exception handlings for unauthorized requests, so let’s add it right away.

12. By the way, in order to use JWT we need to specify the initial configuration. JWTSessions gem by default it uses HS256 algorithm, and it needs an encryption key provided.

Also, on default the gem uses redis as a token store, so you’ll need a working redis-server instance. However, there’s a possibility to select memory as a token store (can be useful in test env). More info about specific settings can be found in the README.

13. Now, let’s create a signup endpoint. With token-based sessions and SPA we have 2 most common options of where to store the tokens on the client — cookies and localStorage. It’s up to developer to decide where they are going to store the tokens. While making the decision, keep in mind — cookies are vulnerable to CSRF and localStorage is vulnerable to XSS attacks. CSRF vulnerability is solvable - I usually prefer http-only cookies as the most secure token store.

jwt_sessions gem itself provides the set of tokens — access, refresh, and CSRF for the cases when cookies are chosen as the token store. With this being said, let’s use cookies together with the CSRF token provided by the gem (the gem automatically manages the CSRF validations when JWT is passed by request cookies).

The session within the gem is represented as a pair of tokens — access and refresh. Access token has a short life span (default 1 hour), and refresh has a relatively long life span (2 weeks). Expiration times are configurable. Refresh token is used to renew the access once it’s expired.

While it makes sense to pass refresh token to external API services or mobile applications — JS clients are usually not secure enough to store the precious refresh token. It’s up to developer to decide which info to pass or not to pass to JS. The jwt_sessions gem provides the possibility to issue a new access token by passing the old expired one, so we can avoid passing the refresh token to JS client. As both refresh and access tokens are linked to each other it will be easy to detect if the access has been stolen from the JS client and flush the leaked session (2 users —the original user and the attacker eventually will have 2 different access tokens pointing to the same refresh token).

Now for real, let’s create the signup endpoint. The endpoint must create users, assemble the JWT payload, and pass it via cookies with the response as well as the CSRF token via response body.

Specs to ensure the signup works.

14. Now we can build a sign in controller.

And specs for signin.

15. Here goes the refresh endpoint. As we’re building an endpoint for web client — we’ll be renewing a new access with the old expired one. Later we can create a separate set of endpoints to be used by other API-consumers (mobile/etc) which will operate via refresh tokens, but for this case we’re not going to risk it and to show the refresh token to the cruel outer world.

We’re expecting only expired access tokens to be used for refresh, so within the refresh_by_access_payload method a block is passed with unauth exception. Optionally, within the block we can notify the support team, flush the session or skip the block and ignore this kind of activity.

JWT library automatically checks expiration claims, and to avoid the exception for an expired access token we’ll be using claimless_payload method.

Refresh specs:

16. Almost there. This is the time to build the todos controller.

To make it work we’ll also need current_user, so let’s add it.

After the token is authorized we can dig into the payload and fetch whatever we decided to store within. In our case it’s user_id .

Generic todos specs:

17. We’re almost set with the API, it’s possible to sign up, to sign in, to refresh an access_token and to manage todos. But while we’re here, let’s also add ability to log out.

18. To allow JS client to send requests to the API we’ll need to set up CORS.

Routes configuration: