Demande d’autorisation avec JWT (JWT Bearer Authorization Grant)

La particularité de ce flux est d’interroger le point d’accès token sans les identifiants (credentials) de l’application cliente, mais en passant un jeton JWT signé.

Spécification du flux JWT Bearer

(traduction d’extrait de la spécification draft-ietf-oauth-jwt-bearer-07 section 1)

2.1. Utilisation de JWT en tant qu’accord d’autorisation (Authorization Grants)

Pour utiliser un jeton JWT pour la demande d’autorisation, émettez une requête de jeton d’accès telle que définie dans la section 4 de la spécification ’Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants [I-D.ietf-oauth-assertions]’ avec les valeurs de paramètres et encodages spécifiques suivants.

La valeur du paramètre "grant_type" DOIT être "urn : ietf : params : oauth : grant-type : jwt-bearer".

La valeur du paramètre "assertion" DOIT contenir un seul JWT.

Le paramètre "scope" peut être utilisé, tel que défini dans la spécification ’...’, pour indiquer la portée demandée.

L’authentification du client est facultative, comme décrit dans la Section 3.2.1 de OAuth 2.0 [RFC6749] et par conséquent, le "client_id" n’est nécessaire que lorsqu’un type d’authentification client qui repose sur ce paramètre est utilisé.

...

3.1. Traitement des accords d’autorisations

Les autorisations JWT peuvent être utilisées avec ou sans authentification ou identification du client. Que l’authentification du client soit requise ou non en conjonction avec un accord d’autorisation JWT, ainsi que les types d’authentification des clients pris en charge, sont des décisions de politique à la discrétion du serveur d’autorisation.
Cependant, si les informations d’identification du client sont présentes dans la demande, le serveur d’autorisation DOIT les valider.

Commentaire sur la sécurité du flux JWT Bearer

L’application cliente doit posséder un jeton JWT valide. Si elle l’a obtenu par OpenID Connect, elle ne peut l’avoir fait qu’en envoyant les identifiants d’application au point d’accès Token (dont le secret de l’application), et c’est précisément ce que le flux permet d’éviter. La solution : l’application cliente doit posséder la clé privée (ou y accéder) afin de générer le jeton JWT à son niveau. Cela nous place dans le cas très particulier des "applications de confiance". Nous savons que ce type d’applications doit se trouver dans un espace de communication protégé par TLS.

Ce flux peut être utilisé pour permettre à une application cliente d’accéder à une ressource protégée sans autorisation de l’utilisateur final (on considère que les données protégées sont la propriété de l’application). Dans cette configuration, nous sommes dans un flux comparable à l’Autorisation de Serveur à serveur (Client Credentials Grant), avec une sécurité supplémentaire : le jeton JWT signé apporte une certitude sur l’identité de l’application cliente.

Test du flux JWT Bearer avec OAuthSD

Quoiqu’il en soit, il est possible de mettre ce flux en oeuvre avec OAuthSD. Le script ci-dessous donne un exemple d’appel au point de terminaison Token avec un jeton JWT fabriqué localement.

PHP

  1. <?php
  2. /* test_JWTBearer.php
  3.  
  4. Usage :  https://oa.dnc.global/oidc/tests/test_JWTBearer.php
  5.  
  6. Author :
  7. Bertrand Degoy https://degoy.com
  8. Credits :
  9. bschaffer https://github.com/bshaffer/oauth2-server-php
  10. Licence : MIT licence
  11. */
  12.  
  13. ini_set('display_errors', 1);
  14.  
  15. // Autoloading by Composer
  16. require __DIR__ . '/../../vendor/autoload.php';
  17. OAuth2\Autoloader::register();
  18.  
  19. //***** Configuration *****
  20.  
  21. $client_id = 'testopenid';   // iss
  22. $user_id = 'bebert';  // subject
  23.  
  24. $server = 'oa.dnc.global';
  25. $token_endpoint = 'https://' . $server . '/token';
  26.  
  27. define('PRIVATE', true);
  28. require_once __DIR__.'/../../oidc/includes/configure.php';        
  29. require_once __DIR__.'/../../oidc/includes/utils.php';
  30.  
  31. //*** End of configuration ***
  32.  
  33. // Connect to database
  34. $cnx = new \PDO($connection['dsn'], $connection['username'], $connection['password']);
  35. // Get private key
  36. $stmt = $cnx->prepare($sql = "SELECT * FROM spip_public_keys WHERE client_id=:client_id");
  37. $stmt->execute(compact('client_id'));
  38. $keyinfo = $stmt->fetch(\PDO::FETCH_ASSOC);
  39. if ( empty($keyinfo) ) {
  40.     $response->setError(401, 'invalid_token', "Invalid aud");   //DEBUG only
  41.     $response->send();
  42.     die;
  43. }
  44. $private_key = $keyinfo['private_key'];
  45.  
  46. $grant_type  = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
  47.  
  48. $jwt = generateJWT($private_key, $client_id, $user_id, 'https://' . $server);
  49.  
  50. passthru("curl " . $token_endpoint . " -d 'grant_type=$grant_type&assertion=$jwt'");
  51.  
  52. /**
  53. * Generate a JWT
  54. *
  55. * @param $privateKey The private key to use to sign the token
  56. * @param $iss The issuer, usually the client_id
  57. * @param $sub The subject, usually a user_id
  58. * @param $aud The audience, usually the URI for the oauth server
  59. * @param $exp The expiration date. If the current time is greater than the exp, the JWT is invalid
  60. * @param $nbf The "not before" time. If the current time is less than the nbf, the JWT is invalid
  61. * @param $jti The "jwt token identifier", or nonce for this JWT
  62. *
  63. * @return string
  64. */
  65. function generateJWT($privateKey, $iss, $sub, $aud, $exp = null, $nbf = null, $jti = null)
  66. {
  67.     if (!$exp) {
  68.         $exp = time() + 1000;
  69.     }
  70.  
  71.     $params = array(
  72.         'iss' => $iss,
  73.         'sub' => $sub,
  74.         'aud' => $aud,
  75.         'exp' => $exp,
  76.         'iat' => time(),
  77.     );
  78.  
  79.     if ($nbf) {
  80.         $params['nbf'] = $nbf;
  81.     }
  82.  
  83.     if ($jti) {
  84.         $params['jti'] = $jti;
  85.     }
  86.  
  87.     $jwtUtil = new OAuth2\Encryption\Jwt();
  88.  
  89.     return $jwtUtil->encode($params, $privateKey, 'RS256');
  90. }

Télécharger

En cas de succès, le serveur retourne quelque chose comme :

{"access_token":"8d2bea8d956f74030d8837deacd6154dbc0df262","expires_in":3600,"token_type":"Bearer","scope":"basic"}