Skip to main content
Nous publions des mises à jour fréquentes de notre documentation, et la traduction de cette page peut encore être en cours. Pour obtenir les informations les plus actuelles, consultez la documentation anglaise.

Principes de base de l’authentification

Découvrez les différentes façons de s’authentifier avec quelques exemples.

Dans cette section, nous allons nous concentrer sur les principes de base de l’authentification. Plus précisément, nous allons créer un serveur Ruby (à l’aide de Sinatra) qui implémente le flux web d’une application de différentes manières.

Vous pouvez télécharger le code source complet de ce projet à partir du référentiel platform-samples.

Inscription de votre application

Pour commencer, vous devez inscrire votre application. Chaque application OAuth inscrite se voit attribuer un ID client et à une clé secrète client uniques. La clé secrète client ne doit pas être partagée. Cela inclut la vérification de la chaîne dans votre référentiel.

Vous pouvez remplir toutes les informations comme vous le souhaitez, à l’exception de l’URL de rappel d’autorisation. C’est l’élément le plus important pour configurer votre application. Il s’agit de l’URL de rappel vers laquelle GitHub Enterprise Server renvoie l’utilisateur après une authentification réussie.

Étant donné que nous exécutons un serveur Sinatra standard, l’emplacement de l’instance locale est défini sur http://127.0.0.1:4567. Remplissons l’URL de rappel comme suit : http://127.0.0.1:4567/callback.

Accepter l’autorisation utilisateur

Avis de désapprobation : GitHub va interrompre le mode d’authentification auprès de l’API à l’aide des paramètres de requête. L’authentification auprès de l’API doit être effectuée au moyen de l’Authentification HTTP de base. Pour plus d’informations, notamment sur les baisses de tension planifiées, consultez le billet de blog.

L’authentification auprès de l’API à l’aide des paramètres de requête, bien que disponible, n’est plus prise en charge pour des raisons de sécurité. Nous recommandons plutôt aux intégrateurs de déplacer leur jeton d’accès, client_id ou client_secret dans l’en-tête. GitHub publiera un préavis pour annoncer la suppression de l’authentification au moyen des paramètres de requête.

Maintenant, commençons à remplir notre serveur simple. Créez un fichier appelé server.rb et collez-y ce qui suit :

require 'sinatra'
require 'rest-client'
require 'json'

CLIENT_ID = ENV['GH_BASIC_CLIENT_ID']
CLIENT_SECRET = ENV['GH_BASIC_SECRET_ID']

get '/' do
  erb :index, :locals => {:client_id => CLIENT_ID}
end

Votre ID client et vos clés secrètes client proviennent de la page de configuration de votre application. Nous vous recommandons de les stocker en tant que variables d’environnement, ce que nous avons fait ici.

Ensuite, dans views/index.erb, collez ce contenu :

<html>
  <head>
  </head>
  <body>
    <p>
      Well, hello there!
    </p>
    <p>
      We're going to now talk to the GitHub API. Ready?
      <a href="https://github.com/login/oauth/authorize?scope=user:email&client_id=<%= client_id %>">Click here</a> to begin!
    </p>
    <p>
      If that link doesn't work, remember to provide your own <a href="/apps/building-oauth-apps/authorizing-oauth-apps/">Client ID</a>!
    </p>
  </body>
</html>

(Si vous n’êtes pas familiarisé avec le fonctionnement de Sinatra, nous vous recommandons de lire le guide Sinatra.)

Notez également que l’URL utilise le paramètre de requête scope pour définir les étendues demandées par l’application. Pour notre application, nous demandons l’étendue user:email pour lire les adresses e-mail privées.

Dans votre navigateur, accédez à http://127.0.0.1:4567. Après avoir cliqué sur le lien, vous devez être redirigé vers GitHub Enterprise Server et accéder à une boîte de dialogue se présentant comme suit : Invite OAuth GitHub

Si vous faites confiance à cette application, cliquez sur Autoriser l’application. Wuh-oh ! Sinatra renvoie une erreur 404. Quelle en est la raison ?

Vous vous souvenez du moment où nous avons spécifié une URL de rappel sur callback ? Nous n’avons pas fourni d’itinéraire pour celle-ci et dès lors, GitHub Enterprise Server ne sait pas où diriger l’utilisateur après qu'il ait autorisé l'application. Nous allons à présent y remédier.

Mise à disposition d’un rappel

Dans server.rb, ajoutez un itinéraire pour spécifier ce que le rappel doit faire :

get '/callback' do
  # get temporary GitHub code...
  session_code = request.env['rack.request.query_hash']['code']

  # ... and POST it back to GitHub
  result = RestClient.post('https://github.com/login/oauth/access_token',
                          {:client_id => CLIENT_ID,
                           :client_secret => CLIENT_SECRET,
                           :code => session_code},
                           :accept => :json)

  # extract the token and granted scopes
  access_token = JSON.parse(result)['access_token']
end

Après une authentification d’application réussie, GitHub Enterprise Server fournit une valeur code temporaire. Vous en aurez besoin pour renvoyer, POST, ce code à GitHub Enterprise Server en échange d’un access_token. Pour simplifier nos requêtes GET et POST HTTP, nous utilisons le client REST. Notez que vous n’accéderez probablement jamais à l’API via REST. Pour une application plus sérieuse, vous devrez probablement utiliser une bibliothèque écrite dans la langue de votre choix.

Vérification des étendues octroyées

Les utilisateurs peuvent modifier les étendues que vous avez demandées en modifiant directement l’URL. Cela peut octroyer à votre application moins d'accès que ce que vous aviez demandé à l'origine. Avant d’effectuer des requêtes avec le jeton, vérifiez les étendues octroyées pour le jeton par l’utilisateur. Pour plus d’informations sur les étendues demandées et octroyées, consultez « Étendues des applications OAuth ».

Les étendues octroyées sont retournées dans le cadre de la réponse d’échange de jeton.

get '/callback' do
  # ...
  # Get the access_token using the code sample above
  # ...

  # check if we were granted user:email scope
  scopes = JSON.parse(result)['scope'].split(',')
  has_user_email_scope = scopes.include? 'user:email'
end

Dans notre application, nous utilisons scopes.include? pour vérifier si l’étendue user:email requise pour extraire les adresses e-mail privées de l’utilisateur authentifié nous a été octroyée. Si l'application avait demandé d'autres étendues, nous les aurions également vérifiées.

En outre, étant donné qu’il existe une relation hiérarchique entre les étendues, vous devez vérifier que vous avez reçu le niveau le plus bas des étendues requises. Par exemple, si l’application avait demandé l’étendue user, seule l’étendueuser:email aurait pu lui être octroyée. Dans ce cas, l’application n'aurait pas obtenu ce qu'elle demandait, mais les étendues octroyées auraient quand même été suffisantes.

La vérification des étendues avant d’effectuer des requêtes ne suffit pas, car il est possible que les utilisateurs modifient ces étendues entre cette vérification et la requête réelle. Dans une telle situation, les appels d’API censés aboutir peuvent échouer avec un état 404 ou 401, ou retourner un autre sous-ensemble d’informations.

Pour vous aider à gérer ces situations, toutes les réponses d’API correspondant aux requêtes effectuées avec des jetons valides comportent également un en-tête X-OAuth-Scopes. Cet en-tête contient la liste des étendues du jeton utilisé pour effectuer la requête. De plus, l’API REST fournit un point de terminaison pour vérifier la validité d’un jeton. Utilisez ces informations pour détecter les modifications apportées aux étendues de jeton et informer vos utilisateurs des modifications apportées aux fonctionnalités d’application disponibles.

Effectuer des requêtes authentifiées

Enfin, ce jeton d’accès vous permet d’effectuer des requêtes authentifiées en tant qu’utilisateur connecté :

# fetch user information
auth_result = JSON.parse(RestClient.get('http(s)://HOSTNAME/api/v3/user',
                                        {:params => {:access_token => access_token}}))

# if the user authorized it, fetch private emails
if has_user_email_scope
  auth_result['private_emails'] =
    JSON.parse(RestClient.get('http(s)://HOSTNAME/api/v3/user/emails',
                              {:params => {:access_token => access_token}}))
end

erb :basic, :locals => auth_result

Nous pouvons faire ce que nous voulons avec nos résultats. Dans ce cas, nous allons les placer directement dans basic.erb :

<p>Hello, <%= login %>!</p>
<p>
  <% if !email.nil? && !email.empty? %> It looks like your public email address is <%= email %>.
  <% else %> It looks like you don't have a public email. That's cool.
  <% end %>
</p>
<p>
  <% if defined? private_emails %>
  With your permission, we were also able to dig up your private email addresses:
  <%= private_emails.map{ |private_email_address| private_email_address["email"] }.join(', ') %>
  <% else %>
  Also, you're a bit secretive about your private email addresses.
  <% end %>
</p>

Implémentation d’une authentification « persistante »

Il ne serait pas pratique de demander aux utilisateurs de se connecter à l’application chaque fois qu’ils ont besoin d’accéder à la page web. Par exemple, essayez d’accéder directement à http://127.0.0.1:4567/basic. Vous obtenez une erreur.

Et si nous contournions l’ensemble du processus « cliquer ici » pour nous souvenir que, tant que l’utilisateur est connecté à GitHub Enterprise Server, il doit être en mesure d’accéder à cette application ? Eh bien, il s’agit précisément de ce que nous allons faire.

Notre petit serveur ci-dessus est plutôt simple. Afin d’intégrer une authentification intelligente, nous allons passer à l'utilisation de sessions pour stocker les jetons. Il en résulte une authentification transparente pour l’utilisateur.

En outre, étant donné que nous conservons les étendues au sein de la session, nous devons gérer les cas où l’utilisateur met à jour les étendues après leur vérification, ou révoque le jeton. Pour ce faire, nous allons utiliser un bloc rescue et vérifier que le premier appel d’API a réussi, ce qui permet de vérifier que le jeton est toujours valide. Après cela, nous allons vérifier l’en-tête de réponse X-OAuth-Scopes pour vérifier que l’utilisateur n’a pas révoqué l’étendue user:email.

Créez un fichier appelé advanced_server.rb, puis collez-y ces lignes :

require 'sinatra'
require 'rest_client'
require 'json'

# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
# if ENV['GITHUB_CLIENT_ID'] && ENV['GITHUB_CLIENT_SECRET']
#  CLIENT_ID        = ENV['GITHUB_CLIENT_ID']
#  CLIENT_SECRET    = ENV['GITHUB_CLIENT_SECRET']
# end

CLIENT_ID = ENV['GH_BASIC_CLIENT_ID']
CLIENT_SECRET = ENV['GH_BASIC_SECRET_ID']

use Rack::Session::Pool, :cookie_only => false

def authenticated?
  session[:access_token]
end

def authenticate!
  erb :index, :locals => {:client_id => CLIENT_ID}
end

get '/' do
  if !authenticated?
    authenticate!
  else
    access_token = session[:access_token]
    scopes = []

    begin
      auth_result = RestClient.get('http(s)://HOSTNAME/api/v3/user',
                                   {:params => {:access_token => access_token},
                                    :accept => :json})
    rescue => e
      # request didn't succeed because the token was revoked so we
      # invalidate the token stored in the session and render the
      # index page so that the user can start the OAuth flow again

      session[:access_token] = nil
      return authenticate!
    end

    # the request succeeded, so we check the list of current scopes
    if auth_result.headers.include? :x_oauth_scopes
      scopes = auth_result.headers[:x_oauth_scopes].split(', ')
    end

    auth_result = JSON.parse(auth_result)

    if scopes.include? 'user:email'
      auth_result['private_emails'] =
        JSON.parse(RestClient.get('http(s)://HOSTNAME/api/v3/user/emails',
                       {:params => {:access_token => access_token},
                        :accept => :json}))
    end

    erb :advanced, :locals => auth_result
  end
end

get '/callback' do
  session_code = request.env['rack.request.query_hash']['code']

  result = RestClient.post('https://github.com/login/oauth/access_token',
                          {:client_id => CLIENT_ID,
                           :client_secret => CLIENT_SECRET,
                           :code => session_code},
                           :accept => :json)

  session[:access_token] = JSON.parse(result)['access_token']

  redirect '/'
end

Le code doit globalement vous sembler familier. Par exemple, nous utilisons toujours RestClient.get pour appeler l’API GitHub Enterprise Server et transmettons nos résultats pour qu’ils s’affichent dans un modèle ERB (appelé cette fois advanced.erb).

De plus, nous disposons désormais de la méthode authenticated? qui vérifie si l’utilisateur est déjà authentifié. Si ce n’est pas le cas, la méthode authenticate! est appelée. Elle effectue le flux OAuth et met à jour la session avec le jeton et les étendues octroyés.

Ensuite, créez un fichier dans les vues appelé advanced.erb et collez-y ce balisage :

<html>
  <head>
  </head>
  <body>
    <p>Well, well, well, <%= login %>!</p>
    <p>
      <% if !email.empty? %> It looks like your public email address is <%= email %>.
      <% else %> It looks like you don't have a public email. That's cool.
      <% end %>
    </p>
    <p>
      <% if defined? private_emails %>
      With your permission, we were also able to dig up your private email addresses:
      <%= private_emails.map{ |private_email_address| private_email_address["email"] }.join(', ') %>
      <% else %>
      Also, you're a bit secretive about your private email addresses.
      <% end %>
    </p>
  </body>
</html>

À partir de la ligne de commande, appelez ruby advanced_server.rb, qui démarre votre serveur sur le port 4567, le même port que celui utilisé lorsque nous avions une application Sinatra simple. Lorsque vous accédez à http://127.0.0.1:4567, l’application appelle authenticate! qui vous redirige vers /callback. /callback nous renvoie à /, et comme nous avons été authentifiés, affiche avancé.erb.

Nous pourrions simplifier ce routage en remplaçant simplement notre URL de rappel dans GitHub Enterprise Server par /. Mais server.rb et advanced.rb s’appuyant sur la même URL de rappel, il nous faut en faire un peu plus pour que cela fonctionne.

De plus, si nous n’avions pas autorisé cette application à accéder à nos données GitHub Enterprise Server, nous aurions vu apparaître la même boîte de dialogue de confirmation que précédemment et nous aurions été avertis.