I am at a stage of the website where I require some authentication – in my case, to create content on the website, i.e. to make posts, I will require the user to be authenticated.
If you have not deployed your application you may want to check earlier posts like this one.
Authentication should always be done using libraries, don’t re-invent the wheel.
For rails, there is a well established, production ready library that can be used, Devise.
If you’re looking for authorization, i.e. you’re looking to fine tune which users can have certain abilities to use different actions, you will want to explore other libraries such as cancancan. This will not be covered in this post as it’s a different topic, however they can be used together.
Getting started with Devise
We can follow the getting started section of Github, add gem 'devise'
to your Gemfile
and execute bundle install
in your terminal.
Once this is complete, execute
rails generate devise:install
We continue to follow the instructions, so I will add:
# development.rb
...
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
# production.rb
...
config.action_mailer.default_url_options = { host: 'api.yazii.co.uk', port: 3000
Update the production host to your actual host.
Next, we generate the model to describe our user, in this case we will call the model ‘User’ as we just want one type of user currently. You can change the User
to Admin
or anything you like.
rails generate devise User
...
Running via Spring preloader in process 70548
invoke active_record
create db/migrate/20210131140502_devise_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users
As you can see, this creates the model and migration files as expected. Before we can use it, we have to migrate the database using: rails db:migrate
There is a lot of different configurations that you can apply to Devise, and there is a lot of documentation available on GitHub for this so I will skip this.
Instead, I will demonstrate how you can quickly start testing your implementations using Postman.
First of all, let’s get the API paths that you’re working on, personally I find the documentation slightly confusing about this, but you can simply type this in your terminal:
rails routes | grep users
It should return something similar to this:
This way, even if you update the routes file, you will get the exact routes that you need to test and use.
The GET new_user_registration
is the HTTP page for user registration. I am looking to implement an API only registration so it’s not quite what I am looking for, instead i look at:
user_registration
PATCH /users(.:format)
PUT /users(.:format)
DELETE /users(.:format)
POST /users(.:format)
Let’s look at what happens when we try to call this endpoint, but before you do, you may need to place the following into your application_controller
respond_to :html, :json
protect_from_forgery with: :null_session, only: Proc.new { |c| c.request.format.json? }
This is fine if you’re using rails and you’re planning to edit the html view (or potentially use the default).
However me and many others would like to use this as API request. The way to enable Devise to work with API mode is to modify the route like so:
devise_for :users, defaults: { format: :json }
with default format of json, Devise will no longer send the html response by default. Let’s see what happens when we post a registration request with empty body:
This JSON response is perfect for API only backend implementation and single page applications, such as with react front end.
Let’s put in some ‘valid’ data to test the API;
{
"user": {
"email": "test123@email.com",
"password": "password123"
}
}
You can then use the sign_in
endpoint to log your user in:
And that’s it! you have a basic authentication model incorporated to your site.
Don’t forget to actually make your actions use the authentication by adding this to relevant functions and controllers:
before_action :authenticate_user!
Bonus section – Add another parameter, username
By completing the above, you will have the basic auth implementation working in the backend, i.e. you can register using email and password.
If you want to add an additional field, such as email you’ll require few more small steps.
- Application controller must permit the additional parameters (or override them in Devise controller if you have it)
- Create migration for the new columns
That’s pretty much it, let’s try it!
In your application_controller.rb
file add the following content:
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
devise_parameter_sanitizer.permit(:account_update, keys: [:username])
end
This allows the username
parameter to be passed (along with defaults) to sign_up
and account_update
.
Check more about sanitiser here if you’re curious.
Now create your migration for user model, mine looks like this:
def change
add_column :users, :username, :string, limit: 255, null: false, unique: true
end
I can do it like this because I have no data, but if you have some, the null validation will fail. If that’s the case, you may want to remove this constraint.
After running rails db:migrate
you can start the server and try it with Postman.
And here’s one for signing in:
And that’s it, we can see the username field is now present in our sign in responses.
small tip
if you wish to sign in using a different field to email
, find this file: config/initializers/devise.rb
and check the section on:
# config.authentication_keys = [:email]
By default, the authentication keys are email, but you can change it to username
for example.
Good luck!