Échange de données 1

4.1- REST

4.2- Authentification

4.3- Exercices

Il est fréquent de vouloir restreindre l'accès à certaines ressources d'un service web, donc il faut être en mesure d'authentifier qui effectue la requête. On peut mettre en place une technique personnalisée pour la gestion des identifiants, mais il existe un mécanisme standard qui facilite l'implémentation via les headers.

Réponse initiale
WWW-Authenticate: Basic realm="zone restreinte!!!"
Requêtes subséquentes, authentifiées
Authorization: Basic ZXRkOnNoYXdp

Les identifiants sont joints par : et encodé en Base64, ex: etd:shawi devient ZXRkOnNoYXdp

Sinatra

📚 Sinatra Basic HTTP Auth

rb
Simple, toutes les routes
require "bundler/inline"

gemfile do
source "http://rubygems.org"

gem "sinatra-contrib"
gem "rackup"
gem "webrick"
end

require "sinatra/base"
require "sinatra/reloader"

class SimpleAuth < Sinatra::Base

configure :development do
register Sinatra::Reloader
end

# Pour toutes les routes
#
# ATTENTION, il faut redémarrer le serveur
# après avoir activé l'authentification
use Rack::Auth::Basic do |username, password|
# Le mecanisme de validation
# peut etre personnalisé, ex:
# fichier, BD, etc.
username == "admin" && password == "pwda"
end

before do
@request_user = env['REMOTE_USER']
# pour eviter de répéter dans chaque route
# le @ defini une variable membre
# https://docs.ruby-lang.org/en/3.1/syntax/assignment_rdoc.html#label-Instance+Variables
# http://ruby-for-beginners.rubymonstas.org/writing_classes/instance_variables.html
end

get "/" do
user = env['REMOTE_USER'] # !!!

"Ahoy #{user}"
end

get "/secret" do
user = env['REMOTE_USER'] # !!!

"Ahoy again, #{user}"
end

get "/secret-again" do
"Ahoy yet again, #{@request_user}"
end

run! if app_file == $0
end
rb
Personnalisée
require "bundler/inline"

gemfile do
source "http://rubygems.org"

gem "sinatra-contrib"
gem "rackup"
gem "webrick"
end

require "sinatra/base"
require "sinatra/reloader"
require "openssl"

SHA_KEY = "th3Superkey!" # Salt de hachage

class CustomAuth < Sinatra::Base

configure :development do
register Sinatra::Reloader
end

# On ne veut pas enregistrer de mot de passe en clair
# le hachage sha256 permet d'empecher une fuite
# des mots de passe pouvant amener a une attaque subsequente si réutilisé
# https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#background
def sha256(value)
nil if value.nil? || value.empty?

OpenSSL::HMAC.hexdigest("sha256", SHA_KEY, value)
end

def authorize
auth = Rack::Auth::Basic::Request.new(request.env)

return unless auth.provided? && auth.basic? && auth.credentials

username, password = auth.credentials
# auth.credentials est ["username", "password"]
# https://docs.ruby-lang.org/en/3.1/syntax/assignment_rdoc.html#label-Array+Decomposition

if username == "admin" && sha256(password) == "71b0e3e2c9d25193a93bf31beaed7c3cf08d2d39a37e52f79366af1b7f6bf9d6" # sha256("pwda")
@user = username
end
end

# Avant chaque route
before do
authorize
# trouver l'utilisateur authentifié, s'il existe
end

def guard!
auth_required = [
401,
{ "WWW-Authenticate" => "Basic" },
"Provide a username and password through Basic HTTP authentication"
]

# HALT interrompt IMMEDIATEMENT la requête et retourne le resultat
halt auth_required unless @user
end

get "/" do
"Welcome!"
end

get "/hey" do
message = @user || "[anonymous]"

"Hey #{message} :)"
end

get "/private" do
guard! # Oblige l'authentification pour cette route

"Ahoy #{@user}"
end

run! if app_file == $0
end

Faraday

require "bundler/inline"

gemfile do
source "http://rubygems.org"
gem "faraday"
end

require "faraday"

#
# POUR LE SERVEUR PERSONNALISÉ
#

server = Faraday.new "http://localhost:4567"

def show(response)
puts response.status

puts "---"
puts response.headers
puts "---"

puts response.body

puts "\n\n"
end

def header(message)
puts "========================"
puts "========================"

puts message

puts "========================"
puts "========================"

puts "\n\n"
end


header "PAS auth"

show server.get("/")

show server.get("/hey")

show server.get("/private")


header "Auth PAR REQUETE"

# Auth manuelle, pour chaque requete
require "base64"

show server.get("/private", nil, {"Authorization" => "Basic #{Base64.encode64("admin:pwda")}"})

show server.get("/hey", nil, {"Authorization" => "Basic #{Base64.encode64("admin:pwda")}"})

show server.get("/hey")


header "Auth PAR CONNEXION"

# Helper sur la connexion
# tant que la connexion existe,
# les infos sont utilisées

server.set_basic_auth "admin", "pwda"

show server.get("/private")

show server.get("/hey")

# On peut egalement configurer l'authentification
# a la creation de la connexion https://lostisland.github.io/faraday/#/middleware/included/authentication

👉 Recipeasy, suite