DEV Community

Brittany
Brittany

Posted on

Day 14: #100DaysofCode - Finalized my Sinatra Project -Security

My Sinatra Project

SECURITY

Something that became very important during the building of my Sinatra project was SECURITY. It is important that once your application is deployed that you ensure that you are taking proper precautions to make sure that your application is secure from cyber attacks. While building my first Sinatra project, I learned a lot about some of the most basic and simple ways to make sure that your project is secure.

1. gitignore

Some may not agree, but I feel like every project on Github should have a .gitignore file. A .gitignore file allows us to "hide" certain files by preventing those files from being pushed/tracked by git. It is very simple to use, just create a file named .gitignore and then add the name of the file that you want to ignore. So for example, I had a notes file that I wanted to keep for myself and I did not want the world/github to know about. I created my .gitignore file and then created a file named secret_notes.txt.

Within my .gitignore file, I added the following:

secret_notes.txt
Enter fullscreen mode Exit fullscreen mode

It was that simple.

If you recall from my previous post , when building a Sinatra project, your environment will have a "SINATRA_ENV" which defines your deployment environment, configures our database, and requires all the files in our app.require a password. My project was no different, my environment looked like this:

ENV['SINATRA_ENV'] ||= "development"

if ENV['SINATRA_ENV'] == "development"
  require_relative "../secrets"
end

require 'bundler/setup'
Bundler.require(:default, ENV['SINATRA_ENV'])

ActiveRecord::Base.establish_connection(
  :adapter => "sqlite3",
  :database => "db/#{ENV['SINATRA_ENV']}.sqlite"
)

Enter fullscreen mode Exit fullscreen mode

The above requires my "../secrets", but when you go to my Github repo the secrets file is not available, as you may have guessed, the file was placed in my gitignore file and although you can not see it I will tell you that I placed a secure sessions password and api keys in the secrets.rb file.

2. Securing Users Password

One way to protect the users interacting with your application is to protect their passwords. An easy way to do this is to use the bcrypt-ruby gem. The gem essentially takes a users password and encrypts it by using a hash algorithm.

I used bcrypt in my application by adding the gem to my gemfile by running: bundle add bcrypt

The bcypt Ruby gem provides you with has_secure_password method. The has_secure_password method encrypts passwords by hashing and salting the passwords and generating a password_digest. bcrypt requires that you use password_digest as your attribute name in your user migrations table. I did that in my users table, like this:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :username
      t.string :email
      t.string :location
      t.integer :age
      t.string :password_digest
      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Then within my users model, I made sure add the has_secure_password macro inside of my User class, like this:


class User < ActiveRecord::Base 

has_secure_password
has_many :watchlists
has_many :stocks, through: :watchlists
validates :username, uniqueness: true
validates :username, presence: true
validates :email, presence: true

end
Enter fullscreen mode Exit fullscreen mode

2. Sessions & User Authentication

A great way to ensure that someone is who they claim to be when they sign up and login is to use cookie and session based authentication.

What is cookie and session based authentication?
Cookies and Sessions are used to store information. Cookies are only stored on the client-side machine, while sessions get stored on the client as well as a server.

What that means is that once you sign up a cookie is saved on the browser and a session is stored on the server. From then on every time you you log in or make future request, some of data stating that it is you gets sent with your HTTP request, confirming that you are an authenticated user.

To set up my sessions I enabled sessions and created a sessions secret to my applications_controller.

  enable :sessions
  set :session_secret, ENV['SESSION_PASSWORD']
Enter fullscreen mode Exit fullscreen mode

3. Secure password

How did I create a secure sessions password for my environment? I generated a secured random number by using the sysrandom gem. It is recommended that everyone use a gem similar to this to generate secure passwords. Simply do the following in your terminal to get a secure password:

First install the sysrandom:

gem install sysrandom
Enter fullscreen mode Exit fullscreen mode

Then run the following, which will generate a random string of hexadecimals:

ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
Enter fullscreen mode Exit fullscreen mode

It should return something similar to this:

5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75
Enter fullscreen mode Exit fullscreen mode

I used that number to require it in my environment, I also required my api numbers provided by the IEX CLOUD api.

My secrets.rb file looked like this:

ENV['SECRET_TOKEN_ID'] = "sk_oiherbnbdlfoiuhejkwbf9ubkjdhfuihf"
ENV['PUBLISHABLE_TOKEN_ID'] = "pk_kjdhfewuohjwni23yhuijkebiwr"
ENV['SESSION_PASSWORD'] = "5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75"
Enter fullscreen mode Exit fullscreen mode

3. Sanitizing User Input

Input sanitization describes cleansing and scrubbing user input to prevent it from jumping the fence and exploiting security holes. But thorough input sanitization is hard. While some vulnerable sites simply don’t sanitize at all, others do so incompletely, lending their owners a false sense of security.

Simply put, when a user is going to your website, they may not have the best intentions. For me, I wanted a user to come to my website and create a Watchlist name through a form like this:

<h3>Create a New Watchlist</h3>

<form action="/watchlists" method="POST">
  <input type="text" name="watchlist[name]" id="watchlist_name" placeholder="Watchlist Name"><br>
  <input type="submit" value="Create Watchlist">
</form>
Enter fullscreen mode Exit fullscreen mode

But what if instead of the user inputting text, they inserted a piece of javascript that generated a pop up box on the application, something like this:

<script>
function myFunction() {
  alert("Hello! I am an alert box!");
}
</script>
Enter fullscreen mode Exit fullscreen mode

What if the user created a script that jeopardized my entire application and created a pop up box for all users using my application. There are ways to prevent this and I learned two different ways to avoid this kind of attack while building this project.

Using The Sanitize Gem

The first way I learned was to use the sanitize gem to avoid SQL injection, cross-site scripting (XSS), and remote file inclusion (RFI).

The gem allows us to Sanitize.fragment(html) to

I chose to use Sanitize.fragment(html) which uses its strictest settings by default, which means it will strip all HTML and leave only safe text behind. This gem is helpful because you can personalize it to allow certain html elements in the input from users. Feel free to look at the gem docs for more examples.

Personally, I used the gem in my post '/watchlists' within my watchlist_controller after a user creates a new name for a watchlist, it will Sanitize the user input and save it after it is Sanitized, like this --

   post '/watchlists' do
     authenticate
    if params[:watchlist][:stock] == nil
     @watchlist = current_user.watchlists.new(name: Sanitize.fragment(params[:watchlist][:name]), user_id: params[:user_id])
      if @watchlist.save
        flash[:message] = "You have successfully created a watchlists."
      redirect "/watchlists"
Enter fullscreen mode Exit fullscreen mode

Using .Gsub

When you google grep and gsub you'll see something like this:
Grep search for matches to argument pattern within each element of a character vector: they differ in the format of and amount of detail in the result and .gsub replaces all occurrences. If replacement contains backreferences which are not defined in pattern the result is undefined (but most often the backreference is taken to be "")

To grasp a better understanding, I collaborated with some of my classmates and learned how to prevent a user from inputting inappropriate data into my application while signing up. One way is by using grep, regex and .gsub.

Within my signup controller you will see that the post signup has the following information:

  post '/signup' do
      @u = User.new(:username => params[:username].gsub(/[\<\>\/]/, ""),
                          :password => params[:password],
                          :email => params[:email].gsub(/[\<\>\/]/, ""),
                          :location => params[:location].gsub(/[\<\>\/]/, ""),
                          :age => params[:age].gsub(/[\<\>\/]/, ""))
      if @u.save
          session[:user_id] = @u.id
          redirect "/watchlists"
      else
          erb :'sessions/signup'
      end
  end
Enter fullscreen mode Exit fullscreen mode

The .gsub searches for matches in my argument pattern (/[\<\>\/]/, "") and if it matches it will result in an undefined/blank answer. So if someone signs up and puts <h1> Hello</h1> as there username. It will result in a username of h1helloh1 removing the <> tags and essentially not effecting the application at all, but instead gives the user a ridiculous username.

I learned a lot about how to protect my application and the individuals using my application while building this application and look forward to learning more ways to protect future projects that I build.

Resources
Password_digest column in User migration table

Add Authentication to Your Rails App With bcrypt

Feel free to use my Sinatra Project and contribute to it.

Song of the day

Top comments (0)