SAML2jwt connects your SP to WAYF in few lines of code

Many developers find it difficult to work with the SAML2 protocol and XML — which up until now has been unavoidable when integrating web services with WAYF. Rather more manageable, typically, is working with JSON and JSON Web Tokens (JWT) — already familiar to many developers from doing e.g. OAuth or perhaps OIDC. That is why WAYF has now developed and launched an interface making it feasible to integrate a web service with WAYF in just 25-75 lines of JWT handling code — without any XML or SAML2. The interface is a micro-service we call SAML2jwt. The idea is for the service to send and receive standard XML SAML2 protocol traffic — but, through SAML2jwt, before sending and after receiving, respectively, have WAYF translate all relevant information into an easily manageable JWT. The only WAYF-related dependency in the service's application code will then be a JWT library of your own choice (including crypto-functions for validating JWT signatures).

Protocol

The flow for a web service receiving a user login using SAML2jwt goes like this:

  1. The service receives a login request from the user's browser.
  2. The service calls https://wayf.wayf.dk/saml2jwt (server2server) using HTTP-GET with parameters issuer and acs added. The value of issuer is set to the protocol name (entityID) for the service at WAYF (an agreed-upon URI), that of acs to the URL where the service is to receive the authentication response later on.
  3. The service receives a HTTP response from SAML2jwt, then passes the headers from this response on to the browser. Take care here to use a library that doesn't follow redirects automatically.
  4. The service receives a HTTP-POST from the browser which is the raw SAML2 login response from WAYF.
  5. The service POSTs the content of the POST received to https://wayf.wayf.dk/saml2jwt (server2server) with parameters issuer and acs added with the same values as in the original GET request above.
  6. The service receives a JWT from SAML2jwt, splits it into its constituent parts (head, body, signature), and verifies the signature using WAYF's public key and the agreed-upon signing algorithm.
  7. The service processes the user info contained in the JSON token (the body of the JWT) accoding to its business logic. The attributes of the user authenticated is keyed in the JWT body by their names in the schemas employed (e.g. 'eduPersonPrincipalName' or '1.3.6.1.4.1.5923.1.1.1.6')

The service must be known by WAYF (i.e., registered in WAYF's metadata registry), just like any regular SAML2 service provider. Soon, however, we will also make available a stand-alone edition of SAML2jwt that you can run on as a REST service on the same machine as your business application, thus removing your app's dependency on WAYF; all this locally-run binary will have to “know” is the location of your app's SSO private key on the machine and that of your federation's MDQ service — a federation which may well have a full-mesh architecture.

Example

Just below find a snippet of PHP code corresponding to what will suffice in an application receiving logins from WAYF using JWT and SAML2jwt:

<?php
$publicKey = // prod key
'-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA54K3rUT473T2Ot5VRXW0UdB72beHrXZbfw5q5Z+1VWvAfQImlgIUYAxQFwMWQ+ChwD5ekHapp0X792cSsgNtNWDGrJ7AHRM5aYyioySeuSZLTHQCJEcgG2TMhYVqb4TAa0UYDkfDeyMY+gNeDhwZPvfW6gKS2wfkQu354UNdIE5SDHIrNl/w1NdFsuSwh0/E2BnTh7klrAWjVydhtNVhByV4yo5hjesgNfEDwFObhlgI2TEs7S/tgrv6Z8XlPVDkLsVeK+hAj4DaaL+oaXAu36gPzWN1iSL+/sCldmLSx1xGi8D0fjIUK/i1Pl/8+W7xgnxpdIaknyABM+2Rj26X1wIDAQAB
-----END PUBLIC KEY-----';

$saml2jwt       = 'https://wayf.wayf.dk/saml2jwt';
$idplist        = []; //['idp.entity.test'];

$params = ['acs'    =>  'http://sp.entity.test:8080/sp.php',
           'issuer' =>  'sp.entity.test'];

$opts = ['http'=> ['follow_location' => 0,
                  'method'  => 'POST',
                  'header'  => "Content-Type: application/x-www-form-urlencoded",],
         'ssl' => ['verify_peer' => false, 'verify_peer_name' => false]];

if (empty($_POST['SAMLResponse'])) {
  $params['idplist'] = join(',', $idplist);
  $opts['http']['content'] = http_build_query($params);
  file_get_contents($saml2jwt, false, stream_context_create($opts));
  array_walk($http_response_header, function($header) { header($header, false); });
} else {
  $opts['http']['content'] = http_build_query(array_merge($params, $_POST));
  $jwt =  file_get_contents($saml2jwt, false, stream_context_create($opts));
  list($header, $body, $signature) = explode(".", $jwt);
  $ok = openssl_verify("$header.$body", base64url_decode($signature), $publicKey, 'sha256');
  $payload =  $ok === 1 ? json_decode(base64url_decode($body), true) : [];

  header('content-type: text/plain');
  foreach(['iat', 'nbf', 'exp'] as $k ) {
      $payload[$k] = date(DATE_ISO8601, $payload[$k]);
  }
  print_r($payload);
}

function base64url_decode($b64url) { return base64_decode(strtr($b64url, ['-' => '+', '_' => '/'])); }

Easy to test

You can test SAML2jwt quite easily, as a service developer — like this:

  1. Save the above PHP code in a file, sp.php.
  2. Start PHP's bulit-in webserver using the shell command 'php -S 0.0.0.0:8080' in the folder where you stored sp.php.
  3. In your machine's hosts file point the DNS name sp.entity.test to the “server” where you want to run sp.php — e.g. your own PC, if that is where you did the above.
  4. Go to http://sp.entity.test:8080/sp.php in a browser. Using a SAML plugin (in FireFox it could be the SAML Tracer) you can easily follow the login flow in the browser.

This test is automatic in the sense that it initiates a login flow when “calling” http://sp.entity.test:8080/sp.php, then simply screen-dumps the JWT resulting from the flow. Late in the flow expect a warning when going from HTTPS to HTTP.

The test employs WAYF's testing environment, and can only be run by people holding user accounts at the WAYF Orphanage “institution”. If you don't have one already, go to https://orphanage.wayf.dk and request one, then await approval by the WAYF Secretariat.

Client code available on GitHub

Client code for C# is available on GitHub.