Nous allons aujourd’hui reprendre la suite de notre précédent article sur la création d’une API minimaliste avec Grape.
La première partie de ce tutoriel est disponible sur notre GitHub.
Actuellement l’API qui est exposée nous propose un CRUD basique :
GET /api/:version/cars(.:format)
GET /api/:version/cars/:id(.:format)
POST /api/:version/cars(.:format)
PUT /api/:version/cars/:id(.:format)
DELETE /api/:version/cars/:id(.:format)
Nous aborderons tout d’abord la mise à jour de la gem grape
, car son développement est très actif. Je vous invite à suivre toute évolution de cette gem sur son dépôt GitHub.
Nous parlerons également de la mise en place de validations sur les attributs de nos modèles exposés ainsi que d’une authentification basique pour requêter notre API en toute sécurité.
Nous élaborerons une version 2 de notre API qui ne laissera pas le choix à l’utilisateur de spécifier la valeur d’un paramètre d’une voiture, nous le mettrons directement en place au seing de notre API.
grape
Pour mettre à jour la gem grape
dans notre application Rails il suffit de remplacer la ligne correspondante à la gem par celle-ci :
# Gemfile
gem 'grape', '~> 0.10.1'
Une fois la ligne changée il ne reste plus qu’à lancer un bundle install
pour mettre à jour le projet.
$ bundle install
Nous avons déjà des validations présentes sur notre API. Nous allons étoffer le sujet en abordant des validations plus spécifiques sur certains paramètres.
Pour cet exemple nous allons prendre le cas de la création d’une voiture via l’API.
Actuellement notre création de voiture ressemble à cela :
desc "Create a car"
params do
requires :car, type: Hash do
requires :manufacturer, type: String
requires :design, type: String
requires :style, type: String
end
end
post do
Car.create!(params[:car])
end
Nous allons restreindre le type de voiture possible en ajoutant un choix limité sur notre attribut :design
. Pour ce faire nous allons utiliser le paramètre :values
en lui spécifiant deux choix possibles.
Lors de la mise en production d’une telle contrainte sur la validation d’un enregistrement, n’oubliez pas de fournir une documentation à l’utilisateur final de votre API.
...
requires :design, type: String, values: ["tourer", "racing"]
...
Nous pouvons maintenant tester, et nous remarquons que lorsque le paramètre design
ne correspond pas à l’un des deux précisés au dessus, nous obtenons un message d’erreur.
Nous allons voir maintenant comment vérifier que la chaîne de caractère saisie pour l’attribut :manufacturer
correspond bien à nos attentes.
Pour cela nous allons utiliser une regex grâce au paramètre :regexp
. Pour notre exemple nous allons demander à ce que l’attribut :manufacturer
soit sous la forme “Cccc”.
Le manufacturer
de notre voiture devra donc contenir une majuscule en début de nom.
...
requires :manufacturer, type: String, regexp: /^[A-Z][a-z]+$/
...
Comme pour le paramètre :values
nous obtenons une erreur lorsque l’attribut :manufacturer
ne correspond pas à notre regex.
Grape
nous permet de spécifier des paramètres optionnels. Le paramètre n’est donc pas obligatoire lors de la validation d’un enregistrement.
Pour ce faire nous allons ajouter un attribut doors
en base à notre modèle Car
.
# Création d'une migration
$ be rails g migration AddDoorsToCar doors:integer
# Prise en compte de la migration dans notre BDD
$ be rake db:migrate
Ajoutons maintenant notre attribut optionnel à la création d’une voiture.
requires :manufacturer, type: String, regexp: /^[A-Z][a-z]+$/
requires :design, type: String, values: ["tourer", "racing"]
requires :style, type: String
# Notre paramètre optionnel 'doors'
optional :doors, type: Integer
...
Lors du test de la création d’une voiture nous pouvons remarquer que si nous ne spécifions pas l’attribut :doors
, le nouvel enregistrement se crée. Mais nous obtenons une valeur null
pour l’attribut doors
.
{"id":6,"manufacturer":"Volvo","design":"tourer","style":"4x4","created_at":"2015-02-10T14:21:36.297Z","updated_at":"2015-02-10T14:21:36.297Z","doors":null}
Pour lui spécifier une valeur par défaut il est nécessaire de l’inclure dans notre paramètre optionnel :
optional :doors, type: Integer, default: 3
Nous avons pu en apprendre plus sur les paramètres obligatoires et optionnels ainsi que sur les validateurs values
et default
. Grape en propose beaucoup plus, je vous invite donc à vous rendre sur la documentation des validations
Grape
prend en compte de base l’authentification par Basic Auth, c’est ce que nous allons voir ici.
Toutefois cette authentification est à proscrire en production pour son manque de sécurité, car le mot de passe est transmis en clair. Je vous encourage donc, si vous mettez cette méthode en pré-production, de passer par le protocole HTTPS
qui permettra de chiffrer le mot de passe.
Cette authentification reste néanmoins pratique en développement ou en pré-production.
Son utilisation reste très simple puisqu’elle consiste en la mise en place d’un nom d’utilisateur et d’un mot de passe.
resource :cars do
http_basic do |username, password|
{ 'synbioz' => '4p1' }[username] == password
end
desc "Return list of cars"
...
L’ajout du http_basic à l’intérieur de notre block resource
, va nous permettre de pouvoir protéger tout ce qui se trouve au seing de ce block.
Il existe bien sûr d’autres méthodes d’authentification on peut, au même titre que l’authentification basique, citer l’authentification par “Digest” qui peut apporter un peu plus de sécurité que l’authentification basique.
Le meilleur moyen pour protéger son application à l’heure actuelle est de suivre les standards et de passer par un provider OAuth2
.
Grape nous permet d’utiliser des Middleware
personnalisés pour l’authentification. Nous reviendrons dans un prochain article sur la mise en place d’une authentification par token en utilisant un provider OAuth2
.
Une nouvelle version d’une API est mise en place lors d’un changement majeur dans son fonctionnement. Nous avons créé notre application de telle sorte à pouvoir apporter facilement une nouvelle version à nos utilisateurs.
Le principe aujourd’hui est de choisir le nombre de portes d’une voiture à la place de l’utilisateur. C’est à dire qu’il ne pourra plus spécifier le nombre de porte qu’il souhaite.
Pour ce faire nous allons créer un nouveau fichier pouvant contenir notre API v2.
# Dans car_world_trader/v2/cars.rb
module CarWorldTrader
module V2
class Cars < Grape::API
version 'v2', using: :path
format :json
prefix :api
resource :cars do
desc "Create a car"
params do
requires :car, type: Hash do
requires :manufacturer, type: String, regexp: /^[A-Z][a-z]+$/
requires :design, type: String, values: ["tourer", "racing"]
requires :style, type: String
end
end
post do
Car.create!(params[:car].merge(doors: 5))
end
end
end
end
end
Il ne nous reste plus qu’à monter la version 2 de notre API sur notre router personnalisé :
# Dans car_world_trader/base.rb
module CarWorldTrader
class Base < Grape::API
mount CarWorldTrader::V1::Cars
mount CarWorldTrader::V2::Cars
end
end
Nous pouvons maintenant tester, et nous remarquons que l’enregistrement de la nouvelle voiture contient bien 5 portes.
Tout comme pour la v1 il nous est possible de rajouter d’autres appels API, et de mettre en place une authentification spécifique pour cette partie de l’api.
Aujourd’hui nous avons pu aborder le principe de validation sur les paramètres, la mise en place d’une authentification basique et la mise en place d’une v2.
Dans un prochain article nous nous retrouverons pour approcher l’authentification par ‘OAuth2’ ainsi que la mise en place de messages d’erreurs personnalisés.
Les sources de cet article sont disponibles sur GitHub.
L’équipe Synbioz. Libres d’être ensemble.