The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Authen::NZRealMe - Integrate with RealMe login and identity services (formerly "igovt logon")

DESCRIPTION

Provides an API for integrating your application with the New Zealand RealMe login service and the RealMe assertion service (for verified identity and address details) using SAML 2.0 messaging.

Note: If you are looking at this code as an example implementation of SAML2, then you can safely ignore any code referring to "iCMS" - it is a (deprecated) supplementary API provided by the RealMe IdP, which uses WS-Trust protocols.

The distribution also includes a command-line tool called nzrealme which can be used for:

  • generating certificate/key pairs for signing and SSL encryption

  • creating/editing the Service Provider metadata file

  • creating a bundle (zip file) containing metadata and certs for upload to the IdP

  • generating AuthnRequest URLs

  • decoding/dumping AuthnRequest URLs

  • resolving SAMLart artifact responses and validating the response

Run nzrealme --help for more information about using the command-line tool.

IMPLEMENTATION DETAILS

This module does not implement 100% of the APIs exposed by the RealMe service. It does support both the 'Artifact' binding and the 'POST' binding options.

The module also does not currently implement the RCMS API. Once again, this is because the authors have not yet needed RCMS functionality. Patches are welcome for this too.

GETTING STARTED

You cannot simply drop some config files in a directory and start authenticating users. Your agency will need to establish a Service Provider role with the logon service and complete the required integration steps. Your first step should be to make contact with the DIA/RealMe team and arrange a meeting.

(Actually, it appears that you can now set up a working integration with at least the RealMe MTS test service without having to engage formally with RealMe or DIA).

CODE INTEGRATION

To integrate the RealMe login service with your application, you will need to:

  1. complete a number of configuration steps (see "CONFIGURATION" below)

  2. link this module into your application to initiate the logon (by redirecting the user to the RealMe login service) and to 'consume' the login information when the user is redirected back to your site

To understand how this module must be linked into your application, it helps to understand the SAML protocol interaction that is followed for each user logon.

The recommended use case will use the 'HTTP-POST' binding as follows:

  Agency Web Site                                 RealMe login server

                     .-------------------------.
                     | 1. user visits agency   |
               .-----|    web site and clicks  |
               |     |   'RealMe login' button |
               v     '-------------------------'
  .-------------------------.
  | 2. SAML AuthnRequest    |
  |    passed back to user  |-------------------------.
  |    via 302 redirect     |                         v
  '-------------------------'           .--------------------------.
   API call                             | 3. Logon service prompts |
                                   .----|    for username/password |
                                   v    '--------------------------'
                      .------------------------.
                      | 4. user enters         |
                      |    username/password   |------.
                      '------------------------'      v
                                       .---------------------------.
                                       | 5. SAML Response returned |
              .------------------------|    via HTTP POST          |
              v                        '---------------------------'
 .-------------------------.
 | 6. FLT to identify user |
 |    extracted from resp. |
 '-------------------------'
  API call

Legacy implementations will use the 'HTTP-Artifact' binding to exchange an 'artifact' (token) for an Assertion using SOAP over a back-channel:

  Agency Web Site                                 RealMe login server

                     .-------------------------.
                     | 1. user visits agency   |
               .-----|    web site and clicks  |
               |     |   'RealMe login' button |
               v     '-------------------------'
  .-------------------------.
  | 2. SAML AuthnRequest    |
  |    passed back to user  |-------------------------.
  |    via 302 redirect     |                         v
  '-------------------------'           .--------------------------.
   API call                             | 3. Logon service prompts |
                                   .----|    for username/password |
                                   v    '--------------------------'
                      .------------------------.
                      | 4. user enters         |
                      |    username/password   |------.
                      '------------------------'      v
                                        .--------------------------.
                                        | 5. SAML 'artifact'       |
              .-------------------------|    returned via redirect |
              v                         '--------------------------'
 .-------------------------.
 | 6. SAML ArtifactResolve |
 |    sent direct to IdP   |--------------------------.
 '-------------------------'                          v
  API call                              .--------------------------.
                                        | 7. SAML ArtifactResponse |
              .-------------------------|    sent back to SP       |
              v                         '--------------------------'
 .-------------------------.
 | 8. FLT to identify user |
 |    extracted from resp. |
 '-------------------------'
  API call returns

The RealMe login server is a SAML Identity Provider or 'IdP'. However the only attribute the login service provides is the 'Federated Logon Tag' - a unique identifier associating the user's login account with the agency web site.

The RealMe assertion server is also a SAML IdP and can be accessed using the same process described above. The response returned from the assertion server will include details of the user's verified identity and/or address (the user must first consent to sharing the requested details). If the 'Assert and Login' option is choosen when integrating with RealMe, the FLT will now also be returned in the response, in either XML or JSON format (another option at integration time). The iCMS back-channel is thus no longer required with the 'Assert and Login' option. This module supports both options.

The agency web site is a SAML Service Provider or 'SP'. The Authen::NZRealMe module implements the SAML SP role on behalf of the agency web app.

To integrate this module with your application, you need to make two calls to its API: the first to generate the authentication request (step 2 above) and the second to resolve the returned artifact and return the Federated Logon Tag (FLT) which identifies the user (step 6 for HTTP-POST binding or steps 6 thru 8 for HTTP-Artifact binding).

It is your responsibility to create a persistent association in your application data store between your user record and the RealMe FLT for that user.

Authentication Request

You will add the RealMe login button image to your application templates. The button does not link directly to the RealMe login server, but instead links to your application which in turn uses the Authen::NZRealMe module to generate a SAML AuthnRequest message encoded in a URL and returns it as a 302 redirect.

The request includes a unique 'Request ID' which you must save in the user session for use when resolving the artifact response later. The example below uses generic framework method calls to save the Request ID and return the redirect URL, you will need to replace these with specific calls for the framework you are using:

  use Authen::NZRealMe;

  my $sp  = Authen::NZRealMe->service_provider(
      type      => 'login',
      conf_dir  => $path_to_config_directory
  );
  my $req = $sp->new_request(
      allow_create => 0,         # set to 1 for initial registration
      # other options here
  );

  $framework->set_state(login_request_id => $req->request_id);

  return $framework->redirect($req->as_url);  # Use HTTP status 302

Your code does not need to explicitly reference the RealMe login service domain or URL - these details are handled automatically by the configuration.

If you wish to use the assertion service rather than the login service, simply change the type parameter when creating the service_provider object:

  my $sp  = Authen::NZRealMe->service_provider(
      type      => 'assertion',
      conf_dir  => $path_to_config_directory
  );

Artifact Resolution

Once the user has provided a valid username and password to the logon service, they will be redirected back to your application, at which point you need to make a second API call to resolve the assertion.

You set up which URL you want the logon service to redirect back to when you generate your service provider metadata (see "CONFIGURATION"). This URL is known as the Assertion Consumer Service or 'ACS'.

If you're using the HTTP-POST binding, you'll need to get the value of the 'SAMLResponse' form parameter and pass it to the resolve_posted_assertion() method (along with the original request_id retrieved from the user's session state).

If you're using the HTTP-Artifact binding, you'll need to get the value of the 'SAMLart' querystring parameter and pass it to the resolve_artifact() method (along with the original request_id retrieved from the user's session state).

In either case, a SAML Assertion will be retrieved. For HTTP-POST, the API call will decode and decrypt the posted response. For HTTP-Artifact, the API call will look after: generating a SAML ArtifactResolve message; passing it to the IdP over a backchannel; accepting the SAML ArtifactResponse message.

Once the SAML assertion is retrieved, the API call will also: validate the assertion (checking digital signature, timestamps and other validity constraints); extract and return the attributes (or error detail) in a response object

The method call will return a response object containing either the attribute details or details of the condition which meant the logon was unsuccessful. In the case of an unexpected error, the method call will generate an exception which you will need to catch and log.

A response from the login service will only include an FLT attribute, whereas a response from the assertion service may contain a number of identity attributes and, optionally, the FLT attribute. See Authen::NZRealMe::ResolutionResponse for details of methods provided to access the attribute values.

Sample code for processing HTTP-POST would look like this:

  my $sp   = Authen::NZRealMe->service_provider(
      conf_dir => $path_to_config_directory
  );
  my $resp = eval {
      $sp->resolve_posted_assertion(
          saml_response => $framework->param('SAMLart'),
          request_id    => $framework->get_state('login_request_id'),
      );
  };
  if($@) {
      # handle catastrophic failures (e.g.: malformed response) here
  }
  if($resp->is_success) {
      $framework->set_state(login_flt => $resp->flt);
      # ... redirect to main menu etc
  }
  elsif($resp->is_timeout) {
      # Present logon screen again with message
  }
  elsif($resp->is_cancel) {
      # Present logon screen again with message
  }
  elsif($resp->is_not_registered) {
      # Only happens if allow_create set to false
      # and user does not have a logon for our site
  }
  else {
      # Some other failure occurred, user might like to try again later.
      # Should present $resp->status_message to user and also give contact
      # details for RealMe Help Desk
  }

Sample code for HTTP-Artifact would be identical except for the 'resolve' method call:

  my $resp = eval {
      $sp->resolve_artifact(
          artifact   => $framework->param('SAMLart'),
          request_id => $framework->get_state('login_request_id'),
      );
  };
  # Process response as for HTTP-POST, above

Note: there are two different categories of 'error': the resolve_artifact() method might throw an exception (caught by eval, details in $@); or a response object might be returned but with $resp->is_success set to false. The details of an exception should be logged, but not displayed back to the user. In the event that your application displays the contents of $resp->status_message you should ensure that you apply appropriate HTML escaping.

For reference documentation about the Service Provider API, see Authen::NZRealMe::ServiceProvider.

CONFIGURATION

This module is configuration-driven - when making an API call, you specify the path to the config directory and the module picks up everything it needs to talk to the RealMe login service IdP (Identity Provider) from metadata files and certificate/key-pair files used for signing/encryption.

Config Files Overview

The files in the config directory use the following naming convention so you just need to point the module at the right directory. The filenames are:

metadata-login-sp.xml

This file contains config parameters for the 'Service Provider' - your end of the authentication dialog - which will talk to the login service. Once you have generated the SP metadata file (see: "Generating Config Files") you will need to provide it to the RealMe logon service to install at their end. You will need to generate separate metadata files for each of your development, staging and production environments.

metadata-login-idp.xml

The login service IdP or Identity Provider metadata file will be provided to you by RealMe/DIA. You will simply need to copy it to the config directory and give it the correct name.

For example, rename this file from the MTS integration bundle:

  MTSIdPLoginSAMLMetadata.xml => metadata-login-idp.xml
metadata-assertion-sp.xml

This file is only required if you are using the assertion service and can be omitted if you are only using the login service.

This file contains config parameters for the 'Service Provider' - your end of the authentication dialog - which will talk to the assertion service. Once you have generated the SP metadata file (see: "Generating Config Files") you will need to provide it to the RealMe logon service to install at their end. You will need to generate separate metadata files for each of your development, staging and production environments.

metadata-assertion-idp.xml

This file is only required if you are using the assertion service and can be omitted if you are only using the login service.

The assertion service IdP or Identity Provider metadata file will be provided to you by RealMe/DIA. You will simply need to copy it to the config directory and give it the correct name.

For example, rename this file from the MTS integration bundle:

  MTSIdPAssertSAMLMetadata.xml.xml => metadata-assertion-idp.xml
metadata-icms.wsdl

This file is only required if you are both using the assertion service and need to resolve the opaque token into an FLT. It can be omitted if you are only using the login service or do not need the user's FLT.

The WSDL file will be provided to you by RealMe/DIA. You will simply need to copy it to the config directory and give it the correct name.

sp-sign-crt.pem

This certificate file is used for generating digital signatures for the SP metadata file and SAML authentication requests. For your initial integration with the RealMe login service development IdP ('MTS'), certificate key-pair files will be provided to you. For staging (ITE) and production, you will need to generate your own and provide the certificate files (not the private key files) to the RealMe login service.

sp-sign-key.pem

This private key is paired with the sp-sign-crt.pem certificate.

sp-ssl-crt.pem

This certificate is used for negotiating an SSL connection on the backchannel to the IdP artifact resolution service.

sp-ssl-key.pem

This private key is paired with the sp-ssl-crt.pem certificate.

Generating Config Files

You must first decide which directory your config files will be stored in. The examples below assume a config directory path of /etc/nzrealme.

Certificate/Key pairs

Once you've decided on a location, you need to generate one or possibly two SSL certificates and their corresponding private keys. The first certificate/key pair will be used for signing the SAML AuthnRequest messages. If you are using the HTTP-POST binding, the IdP will use the public key from this same certificate to encrypt the SAMLResponse. If you are using the HTTP-Artifact binding, you'll need a second certificate/key pair to be used for mutual SSL encryption of communications over the back-channel.

It is not necessary to generate the certificates on the same machine where they will be used however you must have the openssl command-line tools installed on the machine where you wish to generate them.

The process for generating certificates will depend on which environment you are connecting to:

MTS (Development)

You do not need to generate certificates at all for the MTS environment - simply use the files provided in the MTS integration resources pack. Copy them into your config directory and rename as follows:

  mts_mutual_ssl_sp.pem => sp-sign-key.pem
  mts_mutual_ssl_sp.cer => sp-sign-crt.pem
  mts_saml_sp.pem       => sp-ssl-key.pem
  mts_saml_sp.cer       => sp-ssl-crt.pem
ITE (Staging) and PROD (Production)

For both the ITE and production environments you can use the nzrealme tool to generate Certificate Signing Requests which you will then submit to a Certification Authority who will issue signed certificate files. Save them in the config directory using the filenames listed above.

  nzrealme --conf-dir /etc/nzrealme make-certs --env PROD ...

(Note: it used to be possible to use self-signed certificates with ITE - this is no longer possible).

SP Metadata

After you have generated the certificates, you can generate a metadata file with the command:

  nzrealme --conf-dir /etc/nzrealme make-meta

You will be prompted to provide the necessary details and can re-run the command to revise your answers.

Note: Whilst this command should give you a metadata file which passes validation it is likely that you will need to edit the file in a text editor to fine tune the parameters - particularly if you need more than one Assertion Consumer Service.

You will need to provide the SP metadata file to the RealMe login service. For MTS you can upload the SP metadata file directly using the menu option on the MTS site. If you need to revise the file you can simply upload a new version. For ITE and PROD you will need to provide the metadata and also the certificate files to RealMe - who will schedule an upload to the relevant services. You can assemble a 'bundle' of the required files with this command:

  nzrealme --conf-dir /etc/nzrealme make-bundle

TESTING

Normally your application would generate an authentication request URL and redirect the client to it, however it is also possible to generate one from the command-line:

  nzrealme --conf-dir /etc/nzrealme make-req

You can paste this URL into a browser and complete a log on. Once you have logged on you will be redirected back to the URL for the ACS (as specified in the SP metadata file that you uploaded). You can copy the ACS URL from your browser and paste it into the following command to resolve the artifact passed in the URL to an FLT:

  nzrealme --conf-dir /etc/nzrealme resolve <ACS URL> <Request ID>

The ACS URL will contain special characters that may need to be quoted. You'll also need to supply the Request ID which was output by the original make-req command.

API REFERENCE

The Authen::NZRealMe class provides entry points for interactions with the RealMe login service and is also responsible for dispatching the various command implemented by the nzrealme command-line utility.

service_provider( conf_dir => $path_to_config_directory )

This method is the main entry point for the API. It returns a service_provider object that will then be used to generate AuthnRequest messages and to resolve either an encrypted assertion or a returned artifact. Unless you have set up alternative class mappings (see below), this method is a simple wrapper for the Authen::NZRealMe::ServiceProvider constructor.

class_for( identifier )

This method forms half of a simple dependency injection framework. Rather than hard-code the classnames for the various parts of the API, this method is used to turn a simple functional name (e.g.: 'service_provider') into a classname like Authen::NZRealMe::ServiceProvider. This method will also load the package using require.

You would not usually call this method directly - instead you would use the service_provider method which calls this.

register_class( identifier => package )

This method forms the other half of the dependency injection implementation and is used to override the default mappings. The most common reason to use this method is to inject mock object classnames for use during automated testing.

run_command( command, args )

This method is called by the nzrealme command-line tool, to delegate tasks to the appropriate classes. For more information about available commands, see nzrealme --help

init_readline( )

This method is used by the implemntations of 'make-cert' and 'make-meta' when it is necessary to prompt the user for interactive input.

Your application should only need to directly interface with the Service Provider module (as shown above). The service provider will delegate to other classes as required. Reference documentation is available for these other classes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Authen::NZRealMe

You can also look for information at:

Commercial support and consultancy is available through Catalyst IT Limited https://www.catalyst.net.nz.

LICENSE AND COPYRIGHT

Copyright (c) 2010-2022 Enrolment Services, New Zealand Electoral Commission

Written by Grant McLean <grant@catalyst.net.nz> and Haydn Newport <haydn@catalyst.net.nz> and Andrew Maguire <andrew.maguire@catalyst.net.nz>.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See https://dev.perl.org/licenses/ for more information.