Authentication, Mailers and an SPA experience with Rails and Javascript.

02 Aug 2022

If you were playing a game, and it flickers on every click due to page reload, that would be a poor user experience. In this project I used the Javascript fetch API on the client side to communicate with a rails backend during game play, and request a full page reload on the start of every new game.

If you would like to try out the game, it is available here and the code can be found here.

As an aside, I recently got access to DALL-E and the image below was generated with the prompt - ‘A single page web application, paper sketch’.

dall-e image generation

How the game works

Before a user can begin to play, they have to sign up and confirm their email. On the home page, the user is presented with statistics about their past game sessions. There is a leaderboard and also a button to launch the game.

The idea is to play Rock Paper Scissors versus a bot. Once you make a selection, the app sends your selection to the server. On the server side, the bot makes a selection, it is compared to the player’s selection and the winner of the round is determined. The server then returns the round’s result to the client to display. After 5 rounds, the game ends and a little animation on the game container triggers to signify game end.

Why make a round trip to the server every round? You might ask. This follows from the Never Trust the Client maxim, to prevent malicious users from manipulating the outcome of the game.

screenshots

Rails + Javascript Fetch API

Every new game starts with a full page reload. This is fine because it is the start of the game and it feels like starting off with a clean slate. For each new game request, the server creates a new game session linked to the current authenticated user. Making a full page reload for the game play would impact the user experience negatively due to flickers on reload, and hence the need to asynchronously fetch the result of each round.

Keeping track of game session

Game play data like number of rounds, score and result are stored in the session object. It is reset when the game is restarted. Given that session data should be kept under 4kb, it made perfect sense to keep information like this in the session. By default, sessions in rails are encrypted but for an additional layer of security, you can always force SSL connection. This can be done by setting config.force_ssl = true in the application’s config file.

Disabling Turbo Drive

With the way I set up the game, there is a need for a full page reload at the start of every new game. I had to disable Turbo Drive on the Launch New Game button because it doesn’t cause a full page reload and it breaks the game logic.

Devise, the authentication gem, doesn’t play well together with Turbo Drive too, therefore I disabled Turbo drive for every Devise view with a form submission.

Devise gem and rolling your own auth

Instead of rolling your own user authentication system, it is advisable to use battle tested solutions such as the Devise gem for authentication. Most of the edge cases and security loopholes have been covered and it is actively maintained.

Action Mailers

The app requires the user to confirm their signup using an email and also provides a way to recover passwords, this is where action mailers come in. Mailers are somewhat similar to controllers, but one of the ways they differ is that mailers render views on email protocols as opposed to HTTP.

Mail Configurations

You have different options for sending emails like Amazon SES, Gmail, SendGrid, Mailjet etc. There are some limitations with using a personal gmail account as a mail server, therefore I went with Mailjet which has a free tier offer of 200 emails per day.

To interface with action mailer, it is as simple as adding

config.action_mailer.smtp_settings = {
  address: 'in-v3.mailjet.com',
  port: 587,
  user_name: ENV['MAILJET_PUBLIC'],
  password: ENV['MAILJET_PRIVATE'],
  authentication: 'plain',
  enable_starttls_auto: true
}

to the production config.

For this to work with Devise Mailers, it is necessary to set config.mailer_sender = 'no-reply@example.com' in config/initializers/devise.rb, where no-reply@example.com is the sender address you have set on Mailjet.

It is just as important to specify the website’s hostname config.action_mailer.default_url_options = { host: 'https://game.chrisadebiyi.com' } in the production config.

Deployment

For my previous project - weather app, I used Digital Ocean’s app platform, however I went with Fly.io on this project. It is just as easy to use and their free tier offering is really nice for hobby projects. You will need to install their command line tool, login via the terminal and run flyclt launch and it takes care of the rest. It will automatically detect your app platform and build a docker image, then deploy. Environment variables can be set in fly.toml file, and for secrets and private keys you can run flyclt secrets set PRIVATE_KEY=’my-key’ to encrypt them.

About the Design

The sketch was made with Figma and built with vanilla CSS. To apply page specific css, I made extensive use of namespaces because the rails asset pipeline mashes everything up into one big file. For page specific javascript, I used the content_for method together with a named yield.



Link to Code. Link to Live App.

Enjoy!