Graduation

Seth Massarsky
8 min readMar 30, 2021

--

So last week I passed my final project review, and should be set for graduation this coming month. The last several months have really flown by, and I’ve learned a ton along the way. I know I had a little coding experience going into this course, but it was really cool moving from the initial CLI project to where we are now with React.

I’ve had a bit of trouble thinking of exactly what I wanted to discuss in this post, so I think this will mostly be a continuation of the post on the final project. At the time of writing, the project was probably about 80% complete, and still quite buggy. Since then, I’ve completed all of the functionality-related features I wanted in the final product (mainly player statistics on the season and team show pages, and the player code / join a team feature), cleaned up some bugs and hosted both ends of the project on Heroku. Lets start with that last bit, hosting on Heroku, and then I’ll discuss some design concepts that I learned about on the React side, and wind up with some final things that I’d like to work on further with this project.

So I did run into a few roadblocks on the initial setup / push to Heroku (thanks a ton to Chris for helping me out with that!), but the largest roadblock, and what I’d most like to talk about, is how to configure Rails and CORS to handle the authentication / authorization solution I’m using in this application. As discussed in the previous post, aside from authentication / authorization being interesting and a good learning experience, I view this as entirely necessary for this type of application where users can create content and interact with other users. So to speed through the first bit, I used the following links, provided by Chris, to push both applications to Heroku. First, install the Heroku CLI, then configure and push your Rails app, and finally configure and push your React app. Minus running into a few small roadblocks, all this took was a two hour Zoom call to set up!

Now on to the fun stuff. When first looking into authentication / authorization with a separate front end and back end, I made the decision that I wanted to use JWTs stored in HttpOnly cookies. HttpOnly cookies provide some amount of protection against XSS attacks, largely because the cookie cannot be accessed via JavaScript. On login, the user sends a POST request with their credentials. If accepted, the server will create a JWT, store it in a signed cookie

cookies.signed[:jwt] = {value: command.result[:token], httponly: true, same_site: :strict, expires: 1.hour.from_now}

and return the token in the Set-Cookie field of the response header

Set-Cookie: jwt=...; path=/; expires=...; HttpOnly; SameSite=Strict

Subsequent requests to the server then must contain the credentials: 'include' header field, but will automatically send the cookie, while storing it in a way inaccessible to JavaScript. This does not stop someone that has access to the machine already being able to read the cookie directly, say in Chrome dev tools.

In my dev environment, the above solution works, so long as I’m serving the front and back end on the same domain (I hosted the front end at localhost:3000 and back end at localhost:8000); however, we run into a couple problems in production. First, and most importantly, is that the requests are being sent over http, so the login credentials and token are in plain text. Second, once hosted on Heroku, the front and back end do not necessarily share the same subdomain, so SameSite=Strict cannot be used. The solution to both of these issues is using SSL/TLS in the HTTPS protocol.

Before we get into this, I want to be entirely clear that I’m not 100% sure of how this solution is working, but I’ve done a fair amount of testing with Wireshark that indicates that all of the data sent to the back end is over HTTPS and encrypted. That said, configuring a Rails server for SSL requires that you have registered an SSL certificate for your domain. Given my current position, I explored just about all of my options for doing this without registering a certificate. I’m having trouble finding a way to get around to saying this, but it seems that applications hosted with Heroku may support HTTPS requests out of the box. My best guess is that Heroku / AWS has some sort of solution that handles the encryption / tunnel part of the request prior to it hitting the Rails server. Again, I’ve reviewed the packets in Wireshark and all of the fetch requests to the back end do appear to be encrypted. That said, if I had more of a budget for this I’d do it properly and register a certificate so that the Rails server is handling the encryption.

Now that we’re past that disclaimer of sorts, on to the how to. The second issue, having separate subdomains, is solved by setting the SameSite attribute to None. Browsers will only accept this cookie, though, if the Secure attribute is also set, requiring a secure connection(HTTPS). With that in mind, the portion of the login route handling the cookie can be updated as follows:

cookies.signed[:jwt] = {value: command.result[:token], httponly: true, same_site: :none, secure: true, expires: 1.hour.from_now}

And the response header will contain something similar to:

Set-Cookie: jwt=...; path=/; expires=...; secure; HttpOnly; SameSite=None

In order for this to work, though, a couple more things need to be configured. First, CORS origins on the back end, and then baseUrl on the front end, need to be updated to match the newly-provided URLs. Using my application’s domains, CORS origins can be updated as follows:

origins ‘intense-scrubland-75230.herokuapp.com’

(Sidenote — I love this name)

And the baseUrl on the front end (I have this saved in src/config.js exporting baseUrl, imported in the services files discussed later on):

const baseUrl = 'https://polar-savannah-87773.herokuapp.com'

Last, we need to take another look at the back end and the JsonWebToken class created in app/lib/json_web_token.rb. Here, in the encode and decode methods, we reference the application’s secret key(app/config/master.key):

JWT.encode(payload, Rails.application.secrets.secret_key_base)

The problem here is that the master.key file is in your .gitignore file (by default, and for good reason). When you push your application to Heroku, the file will not be included, and your encode / decode methods will not work properly, making it impossible to authorize users. Quick solution here — store the master key in an environment variable using the Heroku CLI (or the website if you want):

heroku config:set RAILS_MASTER_KEY=SUPERSECRETPASSWORDCHANGETHIS

Your super secret password will be the text of the master.key file. It’s possible to create a different key, but you’ll need to make sure the key you set pairs with app/config/credentials.yml.enc. Last, update the encode and decode methods in the JsonWebToken class as follows:

Encode:

JWT.encode(payload, ENV["RAILS_MASTER_KEY"])

Decode:

body = JWT.decode(token, ENV["RAILS_MASTER_KEY"])[0]

With this final change, both sides of the application should be able to be pushed and functional on Heroku!

On to some stylistic things. Along the way with this project, I found two ways of handling where certain things should be that I really liked and utilized throughout the project. Both of them came from this web page, and are separating out your actions, services and constants, and using an index.js file to export the default exports of the files in a folder. I think likely both of these would be better for larger projects, and sort of add unnecessary complexity to a small one (as discussed in my project review); however, I really liked both ideas so probably overused them quite a bit.

For the first, separating out actions, services and constants, this is elaborating a bit on the separation of concerns related to async requests and updating the redux store. In the curriculum, we were taught to write a mapDispatchToProps function that links a component to related actions (could be related to any CRUD actions). The action returns a function that generally will send a fetch request to the server, and then update the redux store with the response.

In the above-linked page, the constants will be created first, where the reducer’s action.type s will be declared. The constants are then imported in the related reducer and action files. Further, the fetch portion of the action is moved to a service file, so the action file sort of works as an intermediary between the component, service and reducer. The general workflow starts in the component, where an action is called. The action first clears the store’s alerts, and updates the relevant other section (teams / team / season / etc) as ‘fetching’. Then the related service(fetch) is called, and response is returned to the action as either JSON or an error. The action takes this response and sends it to the reducer to update the store accordingly. Again, while this was definitely way more than was necessary for a small application like this, I really liked how this separated out different parts of a request and is a nice scalable solution.

Next, the usage of an index.js file. An index.js file appears to be a way to export a folder as a module in JavaScript. Although again, probably just adding complexity in this type of application, I thought it brought a lot of structure to the application and made it something that would be easier to scale over the long run. Mainly, the way that I would use it is for creating components and subcomponents. On the first few pages while I was building out this application, I’d just put all of the components of the same page in one folder. As the project progressed, these folders were getting pretty big, so I decided to start using index.js files and sub-folders. The main component for any given page will be its own folder with one file, itself, which has folders for each of its subcomponents — generally a header, table of some sort, and modals for forms. The subcomponent will have the component, the component’s subcomponents and an index.js file to export the main component. Lets explore an example of a table — the table’s components folder will include a Header.js, THead.js and TBody.js, exported by an index.js file. The above table component then imports these subcomponents from the components folder instead of each individually:

Import in Table component:

import { Header, THead, TBody } from './components'

Index.js file in ./components:

export { default as Header } from './Header'
export { default as THead } from './THead'
export { default as TBody } from './TBody'

Then the table exports from its folder in the index.js file up a step:

export { default as SomeTableName } from './SomeTableName'

Last, the main page container imports each of these sections:

import { PageHeader } from './PageHeader'
import { SomeTableName } from './SomeTableName'
import { Modals } from './Modals'

Again this is definitely over the top for this project, but in a larger project I could see this being very useful. Once you start having components located further and further away from where they’re called, keeping track of relative and absolute paths will get more and more complex — something entirely averted by following this pattern.

Lastly, I wanted to cover some things that I’m looking to change about the project going forward. First and most importantly — I’d really like to figure out setting up the SSL certificate properly. I think that’s going to be a very good learning experience, and I’d think very important in a working environment. Next, I’d like to improve the error handling in the application. As noted in my project review, I’m not using raise / catch, and the application can crash instead of fail gracefully (namely if a fetch fails due to bad internet or the back end being offline). Again a nice learning experience that I think will improve the user experience on the application. Last, just a couple feature-related things I’d like to add. In several areas of the applications, I’ve created links to player / user profiles, but currently these just route to a blank screen. I’d really like to build out these profiles, as well as create chat functionality for teams. I think this will add a lot to the social aspect of the application.

Outside of these, over the next couple weeks I’ll be working on moving my Rails project (the fantasy hockey app) over to React, and starting on the job search path post-graduation. Really looking forward to the weeks to come.

Until next time!

--

--

Seth Massarsky
Seth Massarsky

No responses yet