Welcome to the first lesson of our course, "Securing and Testing Your Ruby on Rails App." Today, we'll be focusing on user authentication, ensuring user passwords are stored securely using Bcrypt and managing authentication with JWT (JSON Web Tokens).
Authentication is a cornerstone of application security, protecting user data, and controlling access to specific parts of your app. Bcrypt
is a proven library for securely hashing and storing passwords, making it computationally difficult for attackers to crack them. JWTs
enable secure transmission of information as a JSON object, facilitating robust authentication management. By the end of this lesson, you'll be equipped to integrate JWT-based authentication in your Ruby on Rails app.
Let's dive right in!
First, we need to create a User
model to store user information, including their securely hashed passwords. This model will encapsulate user-related data and behaviors.
Run the following Rails generator command to create the User
model with username
and password_digest
fields:
Bash1rails generate model User username:string password_digest:string 2rails db:migrate
Explanation:
- The
rails generate model User username:string password_digest:string
command creates a newUser
model withusername
andpassword_digest
attributes. - The
rails db:migrate
command applies the migration, creating theusers
table in the database with the specified attributes.
With the User
model in place, we'll add password hashing functionality using the has_secure_password
method provided by Rails.
Open the user.rb
model file (app/models/user.rb
) and add the following:
Ruby1class User < ApplicationRecord 2 has_secure_password 3end
Explanation:
class User < ApplicationRecord
: Defines theUser
model, inheriting fromApplicationRecord
.has_secure_password
: Uses metaprogramming to add functionalities for securely hashing and storing passwords. It automatically adds attributes likepassword
andpassword_confirmation
to the model, managed in-memory for security purposes.
Now, whenever you create a new user, the has_secure_password
method will ensure the password is hashed using Bcrypt
and stored in the password_digest
field.
Next, let's implement the register
action in the AuthenticationController
, which will handle the creation of new users.
Here’s the code for the register
action:
Ruby1class AuthenticationController < ApplicationController 2 def register 3 user = User.new(user_params) 4 if user.save 5 render json: { message: 'User created successfully' }, status: :created 6 else 7 render json: { error: user.errors.full_messages }, status: :unprocessable_entity 8 end 9 end 10 11 private 12 13 def user_params 14 params.require(:user).permit(:username, :password) 15 end 16end
Explanation:
- The
register
action creates a newUser
object with the provided parameters. - If the user is saved successfully, it returns a JSON response with a success message and a
created
status. - If there are errors, it returns a JSON response with the error messages and a
unprocessable_entity
status. - The
user_params
method ensures only permitted parameters (username
andpassword
) are allowed for user creation.
JWTs provide a robust solution for managing authentication. We'll create a token that can be sent to the client and used to verify user identity in subsequent requests. A token is a string of characters that represents a user's authentication credentials. In JWT, tokens contain user information like IDs and expiration dates, and they are securely signed to ensure authenticity. This allows users to authenticate and be recognized without the server having to store session data.
Here's how to set up the generate_token
method in the AuthenticationController
:
Ruby1private 2 3def generate_token(user_id) 4 JWT.encode({ user_id: user_id }, Rails.application.credentials.secret_key_base) 5end
Explanation:
- The
generate_token
method takes auser_id
as an argument. - It uses the
JWT.encode
method to create a token, encoding theuser_id
with a secret key (Rails.application.credentials.secret_key_base
).
Finally, let’s implement the login
action to authenticate a user and generate a JWT token if the credentials are valid.
Here’s the code for the login
action:
Ruby1def login 2 user = User.find_by(username: params[:username]) 3 if user&.authenticate(params[:password]) 4 token = generate_token(user.id) 5 render json: { token: token }, status: :ok 6 else 7 render json: { error: 'Invalid credentials' }, status: :unauthorized 8 end 9end
Explanation:
- The
login
action finds a user by the providedusername
. - It checks if the user exists and if the
authenticate
method verifies the password. - If successful, it generates a JWT token using the
generate_token
method and returns it in the JSON response with aok
status. - If the credentials are invalid, it returns an error message with an
unauthorized
status.
Lastly, register the new actions in the routes.rb
file:
Ruby1Rails.application.routes.draw do 2 post 'register', to: 'authentication#register' 3 post 'login', to: 'authentication#login' 4 resources :todos 5end
In this lesson, we covered:
- Creating the
User
model and securing passwords withhas_secure_password
. - Implementing the
register
action to create new users. - Setting up JWT-based authentication with the
generate_token
method. - Implementing the
login
action to authenticate users and provide a JWT token.
By achieving these tasks, you've secured your Ruby on Rails application using JWT for authentication. Now, move on to the practice exercises to further solidify your understanding. Great job!