Ruby On Rails : Authentification avec token
Guillaume Briday
3 minutes
Après les articles concernant les moyens d'installer Rails sur différents supports, nous allons aujourd'hui faire un peu plus de concret !
Les tokens d'authentification
Avec Rails 5, il est très facile de configurer Rails pour en faire seulement une API. Je vous laisserai le soin de lire la documentation pour savoir quelles sont les différences avec la version standard.
Lors de la création d'une nouvelle application, il suffit d'ajouter le drapeau --api
:
$ rails new ma_super_api --api
Si votre application n'est pas configurée pour être uniquement une API, ce que je vais présenter ici fonctionne malgré tout !
Introduction
Comme le suggère le titre de l'article, nous voulons avoir la possibilité de s'authentifier avec un token de façon sécurisée. Pour cet exemple, je vais venir greffer l'authentification avec token à une application déjà existante mais il sera très simple de l'adapter si celle-ci est nouvelle.
Notre application
Je vais faire une application qui gère des livres ayant seulement un titre.
$ rails generate scaffold Book title:string
Je souhaite pouvoir accéder à l'ensemble de cette ressource uniquement si je suis authentifié avec un token d'accès. Commençons par ajouter un champ à notre model User, il nous permettra de sauvegarder notre token pour chaque membre.
Générer les tokens
$ rails generate migration AddAuthTokenToUser auth_token:string
Lors de la création d'un membre, nous allons générer automatiquement un token privé pour qu'il puisse s'en servir dans l'API.
Dans le model User, je vais utiliser le callback before_create
pour générer automatiquement un token en vérifiant qu'il soit bien unique pour chaque utilisateur.
# app/models/user.rb
class User < ApplicationRecord
before_create :set_auth_token
private
def set_auth_token
self.auth_token = generate_auth_token
end
Pour générer le token, je me sers directement de la méthode utilisée par has_secure_token
dans active_record.
Si je créer un User, je remarque que le champ auth_token
a bien été rempli automatiquement !
Utiliser les tokens
Rails vient avec la méthode authenticate_or_request_with_http_token
qui va vérifier pour nous le header Authorization
et nous renvoyer le token directement dans un block si ce dernier est valide.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::HttpAuthentication::Token::ControllerMethods
# Ce before_action vérifira le token pour chaque requête.
# Vous pouvez le placer dans les controllers enfants si vous voulez l'authentification
# pour seulement certaines méthodes
before_action :authenticate
protected
def authenticate
authenticate_token || render_unauthorized
end
def authenticate_token
authenticate_or_request_with_http_token do |token, options|
@current_user = User.find_by(auth_token: token)
end
end
à partir de maintenant, chacune des requêtes aux controllers devront avoir un token valide dans le header Authorization
et je peux le vérifier directement avec curl :
$ curl -I http://localhost:3000/books
HTTP/1.1 401 Unauthorized
Et avec un token généré lors de la création d'un utilisateur :
$ curl -IH "Authorization: Token token=coUra7m9yYGBR6SN66bzWefB" http://localhost:3000/books
HTTP/1.1 200 OK
Si je souhaite pouvoir accéder à la liste des films sans token, je peux utiliser le callback skip_before_action
:
# app/controllers/books_controller.rb
class BooksController < ApplicationController
skip_before_action :authenticate, only: [:index, :show]
Et si l'on test avec curl :
$ curl -I http://localhost:3000/books
HTTP/1.1 200 OK
Tout fonctionne comme on pourrait l'attendre mais utilisons les tests pour le vérifier.
Les tests
Je vais utiliser les tests qui ont été générés pour nos Books que nous allons devoir modifier un peu pour passer le header Authorization
. Mais avant tout, je vais rajouter aux helpers une méthode pour encoder les token d'authentification durant mes tests :
# test/test_helper.rb
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
Et enfin, je peux modifier mes tests :
# test/controllers/books_controller_test.rb
require 'test_helper'
class BooksControllerTest < ActionDispatch::IntegrationTest
setup do
@book = books(:one)
# Je créer un User pour obtenir un token
@demo = User.create!({email: '[email protected]', password: 'demodemo', password_confirmation: 'demodemo'})
end
test "should get index" do
get books_url, as: :json
assert_response :success
end
# Je dois ajouter le header 'Authorization'...
test "should create book" do
assert_difference('Book.count') do
post books_url,
params: { book: { title: @book.title } },
as: :json,
headers: {'Authorization' => token_header(@demo.auth_token)}
end
assert_response 201
end
test "should show book" do
get book_url(@book), as: :json
assert_response :success
end
test "should update book" do
patch book_url(@book),
params: { book: { title: @book.title } },
as: :json,
headers: {'Authorization' => token_header(@demo.auth_token)}
assert_response 200
end
# ... Sinon ça ne marche pas
test "should not update book" do
patch book_url(@book),
params: { book: { title: @book.title } },
as: :json
assert_response 401
end
test "should destroy book" do
assert_difference('Book.count', -1) do
delete book_url(@book),
as: :json,
headers: {'Authorization' => token_header(@demo.auth_token)}
end
Tout se passe comme on l'attendait !
Conclusion
Nous avons donc fini cette rapide implémentation de l'authentification par token. L'application est disponible sur ce dépôt GitHub si vous souhaitez.
N'hésitez pas à faire des retours ou proposer des améliorations !
Merci :)