Remote Authentication

In this section you will learn how to configure your PEER instance to allow other authentication mechanism besides the standard local authentication system based on user names and passwords stored locally.

There are three other ways to handle the authentication in PEER:

  • SAML
  • Remote User
  • x509 client certificates

Other Django ways may apply such as LDAP authentication backends but they are out of the scope of this document.

SAML Authentication

With this method your PEER instance is said to be federated with another system that handles the identity of your users. Using standard SAML terminology PEER is the Service Provider and the other system is the Identity Provider.

Actually PEER is an application protected by an embedded native Service Provider implemented using the djangosaml2 library, which in turn, use the PySAML2 library. But enough with the buzzwords, let’s see how to configure the PEER instance in this manner.

There are two things you need to setup to configure the SAML support:

  1. local_settings.py as you may know, it is the main Django configuration file.
  2. PySAML2 specific files such as the certificate and the remote metadata.

Changes in the local_settings.py file

By default SAML support is disabled in PEER and you need to explicitly enable it with the SAML_ENABLED option:

SAML_ENABLED = True

This will change the login and logout pages adding the necessary options to let the user start and finish his session with the help of a SAML Identity Provider.

Some Identity Providers do not handle the logout process very well since it is a quite complex operation not very reliable in all the cases. For this reason it is recommended to set the SESSION_EXPIRE_AT_BROWSER_CLOSE Django option to True when enabling SAML:

SESSION_EXPIRE_AT_BROWSER_CLOSE = SAML_ENABLED

What this mean is that when the user closes the browser application, the session will be destroyed.

Finally, there is a third option that controls if the PEER instance should let any user that comes from a registered IdP to start the session or only allow those users that previously existed in the database. This option is named SAML_CREATE_UNKNOWN_USER and by default it is True:

SAML_CREATE_UNKNOWN_USER = True

By default the PEER instance is configured to store certain fields of the user identity in the Django user object. It is not recommended to change this option unless you know what you are doing since some parts of PEER may stop working. The default value for this SAML_ATTRIBUTE_MAPPING is as this:

SAML_ATTRIBUTE_MAPPING = {
    'mail': ('username', 'email'),
    'givenName': ('first_name', ),
    'sn': ('last_name', ),
    }

Which means that the mail attribute that comes from the SAML assertion will be stored in the Django user fields username and email. The givenName SAML attribute will be mapped to the first_name Django user field and the sn SAML attribute will be mapped to the last_name Django user field.

PySAML2 specific files and configuration

Once you have finished configuring your Django project you have to start configuring PySAML2. If you use this library directly you need to put your configuration options in a file and initialize PySAML2 with the path to that file.

With djangosaml2 you just put the same information in the Django local_settings.py file under the SAML_CONFIG option. Following is a typical configuration for protecting a PEER instance:

from os import path
import saml2
BASEDIR = path.dirname(path.abspath(__file__))
PEER_HOST = 'localhost'
PEER_PORT = '8000'
PEER_BASE_URL = 'http://' + PEER_HOST + ':' + PEER_PORT

SAML_CONFIG = {
  # full path to the xmlsec1 binary programm
  'xmlsec_binary': '/usr/bin/xmlsec1',

  # your entity id, usually your subdomain plus the url to the metadata view
  'entityid': PEER_BASE_URL + '/saml2/metadata/',

  # directory with attribute mapping
  'attribute_map_dir': path.join(BASEDIR, 'pysaml2', 'attributemaps'),

  # this block states what services we provide
  'service': {
      # we are just a lonely SP
      'sp' : {
          'name': 'PEER SP',
          'endpoints': {
              # url and binding to the assetion consumer service view
              # do not change the binding or service name
              'assertion_consumer_service': [
                  (PEER_BASE_URL + '/saml2/acs/', saml2.BINDING_HTTP_POST),
                  ],
              # url and binding to the single logout service view
              # do not change the binding or service name
              'single_logout_service': [
                  (PEER_BASE_URL + '/saml2/ls/', saml2.BINDING_HTTP_REDIRECT),
                  ],
              },

           # attributes that this project need to identify a user
          'required_attributes': ['mail'],

           # attributes that may be useful to have but not required
          'optional_attributes': ['givenName', 'sn'],

          # in this section the list of IdPs we talk to are defined
          'idp': {
              # we do not need a WAYF service since there is
              # only an IdP defined here. This IdP should be
              # present in our metadata

              # the keys of this dictionary are entity ids
              'https://localhost/simplesaml/saml2/idp/metadata.php': {
                  'single_sign_on_service': {
                      saml2.BINDING_HTTP_REDIRECT: 'https://localhost/simplesaml/saml2/idp/SSOService.php',
                      },
                  'single_logout_service': {
                      saml2.BINDING_HTTP_REDIRECT: 'https://localhost/simplesaml/saml2/idp/SingleLogoutService.php',
                      },
                  },
              },
          },
      },

  # where the remote metadata is stored
  'metadata': {
      'local': [path.join(BASEDIR, 'pysaml2', 'remote_metadata.xml')],
      },

  # set to 1 to output debugging information
  'debug': 1,

  # certificate
  'key_file': path.join(BASEDIR, 'mycert.key'),  # private part
  'cert_file': path.join(BASEDIR, 'mycert.pem'),  # public part

  # own metadata settings
  'contact_person': [
      {'given_name': 'Lorenzo',
       'sur_name': 'Gil',
       'company': 'Yaco Sistemas',
       'email_address': 'lgs@yaco.es',
       'contact_type': 'technical'},
      {'given_name': 'Angel',
       'sur_name': 'Fernandez',
       'company': 'Yaco Sistemas',
       'email_address': 'angel@yaco.es',
       'contact_type': 'administrative'},
      ],
  # you can set multilanguage information here
  'organization': {
      'name': [('Yaco Sistemas', 'es'), ('Yaco Systems', 'en')],
      'display_name': [('Yaco', 'es'), ('Yaco', 'en')],
      'url': [('http://www.yaco.es', 'es'), ('http://www.yaco.com', 'en')],
      },
  'valid_for': 24,  # how long is our metadata valid
  }

Note

Please check the PySAML2 documentation for more information about these and other configuration options.

There are several external files and directories you have to create according to this configuration.

The xmlsec1 binary was mentioned in the installation section. Here, in the configuration part you just need to put the full path to xmlsec1 so PySAML2 can call it as it needs.

The metadata option is a dictionary where you can define several types of metadata for remote entities. Usually the easiest type is the local where you just put the name of a local XML file with the contents of the remote entities metadata. This XML file should be in the SAML2 metadata format.

The key_file and cert_file options references the two parts of a standard x509 certificate. You need it to sign your metadata an to encrypt and decrypt the SAML2 assertions.

Note

Check your openssl documentation to generate a test certificate but don’t forget to order a real one when you go into production.

IdP setup

Congratulations, you have finished configuring the SP side of the federation. Now you need to send the entity id and the metadata of this new SP to the IdP administrators so they can add it to their list of trusted services.

You can get this information starting your Django development server and going to the http://localhost:8000/saml2/metadata url. If you have included the djangosaml2 urls under a different url prefix you need to correct this url.

Remote User Authentication

The REMOTE_USER header authentication is a common way to delegate the authentication step to the web server. It doesn’t matter how the web server handles the authentication, when a user is authenticated, the web server puts the user information in a REMOTE_USER header before passing the request into the application.

The PEER application can read the user information from the REMOTE_USER and create a user session for that user. It will create the user in the PEER database if it is not already created.

In this section we will cover how to setup this mechanism in PEER using a simple authentication mechanism with the Apache web server. A htpasswd type file will be use to store user credentials and Auth Basic will be used to challenge the user for entering those credentials. Please note that this is just an example and any authentication method supported by Apache or any other web server will just work the same way.

Web server setup

As said in the previous parragraph a htpasswd file will be use to store the users credentials. So it need to be created:

$ htpasswd -c /tmp/passwords.htpasswd lgs@yaco.es
New password:
Re-type new password:
Adding password for user lgs@yaco.es

As you can see, emails are used as identifiers for PEER users.

Note

Make sure the passwords.htpasswd file is readable for the user running the web server.

Then the Apache web server needs to be configured to handle the authentication. Go to the virtual host section where the PEER application is being served and add this code:

<Location /remote-user-login>
    AuthType Basic
    AuthName "PEER realm"
    AuthUserFile /tmp/passwords.htpasswd
    Require valid-user
</Location>

Note that in the AuthUserFile option you need to put the full path for the file you created in the previous step.

PEER setup

Once the web server is configured to send the authenticated user in the REMOTE_USER request header we need to enable that in PEER.

There are three settings you need to change in the local_settings.py file. First, a middleware to process the header need to be added to the MIDDLEWARE_CLASSES option:

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.RemoteUserMiddleware',  # This is the addition
    'django.contrib.messages.middleware.MessageMiddleware',
)

Then, a new authentication backend need to be added:

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.RemoteUserBackend',
)

Finally, a PEER specific option needs to be enabled to show this authentication option in the login page:

REMOTE_USER_ENABLED = True