"Technologies change, but the basics are the same". That's what my mentor told me years ago when I asked him how I could be a great software engineer. And it's so true especially when it comes to securing an API with a token-based authentication. No matter the framework you use (Spring, Rails, Laravel, etc.), the principle is the same. So in this blog post, we will discuss how to implement it successfully in your Rails application.
The project:
We want to create a backend API for an app like Hackerrank.
Here is the Database Design and Entity-Relationship Diagram (ERD) for the API.
For the entities, we have:
- Users
- Challenges (Programming questions)
- Submissions (User solutions to challenges)
- Categories (Challenge categories)
- Comments (User comments on challenges)
- Ratings (User ratings for challenges)
- Test Cases (Input and expected output for challenges)
Here are some relationships from the diagram above:
- A User has many Submissions.
- A Challenge belongs to a Category.
- A Challenge has many Test Cases.
- Only admins can create, update, or delete challenges, submissions, categories, or test cases.
Now, let's focus on implementing the authentication and authorization for our API. I assume you already know how to install a rails application focused on an API implementation, how to generate controllers and models.
We will go with the basic MVC architecture provided by Rails.
Install the necessary gems
Add this to your Gemfile:
bcrypt will be used for password-hashing.
jwt will be used to successfully log the user in and handle authorization.
Create a secret_key_generator file in your initializers folder and paste this:
Initializers are executed when the Rails application boots. In our case, this initializer generates a random secret key using SecureRandom and sets it as an environment variable (APP_SECRET_KEY). We will use it to decode JWT tokens.
Create the models and migration files
User
Category
Challenge
And since there is a many-to-many relationship between Challenge and Category, let's add the pivot table:
Add the routes
You may have guessed it, but you will need to create a controllers/auth folder. I'll explain why in a few minutes.
Authentication logic
Add this to your users_controller file:
That controller is simply responsible for creating new users and admins.
Create a helpers/token_helper file in your app folder and add this code:
And include it in your application_controller:
Now let's create a sessions_controller in our auth folder and add this code:
What we did there basically is generate a new token when a user logs in and invalidate a token when a user logs out. Remember, the token is used to check if a user session is valid or not. If you don't know how JWT tokens work, check the documentation here.
Now if you request POST /users and POST /login with the correct parameters using Postman, it should work.
Add the Challenge and Category controllers:
As you can see, both controllers check if the user session is valid before executing some controller actions (create, update, delete). They are doing so with authorize_user and authorize_admin.
Add those methods in your application_controller:
Now, try to create a challenge without logging in and you should get an Unauthorized message. Log in, set the current user as admin, try again and it should work.
Et voila, let me know in the comments section if you found this article helpful.