How to :: use authentification providers

External identity providers (called IdP in this doc) are configured in the instance_settings.cson file for your instance. To download this file, you must login with an admin user profile. Then go to the settings page : https://<front-end url>/settings.

In instance_settings there is an array describing all available IdP. Each provider should have a type, below are available types.

  • local: connect with username/password
  • SAML2: delegate authentication to an external provider using SAML2.
  • OIDC: delegate authentication to an OpenID Connect provider

All available types of IdP can be used together and multiple times.

Common properties

  • id: a unique ID for the provider, used to build redirect and metadata urls (we refer to this as provider id in the examples below)
  • name: display name
  • icon: name of an icon representing the provider (we use font awesome)
  • discrete: (boolean) when set to true the provider will be available below other options as a simple html link

SAML2 configuration

There are two main components to the SAML2 configuration:

  • sp : service provider, i.e. your Toucan Toco instance
  • idp : identity provider, i.e. the authentication service

SP configuration

Configuring the SP on Toucan Toco will allow you to create a SP “metadata” file. Most IdPs let you use this metadata file to configure a new SP (Toucan Toco) automatically on their end. You will be able to download this file at the end of this section.

First, we need to gather the following information (the provider id here is the value of the field id in this provider configuration):

  • entityID: by convention we use the url for Toucan Toco backend
  • assertionConsumerService:
  • url: https://<backend url>/auth/provider/<provider id>/acs
  • binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
  • singleLogoutService:
  • url: https://<backend url>/auth/provider/<provider id>/sls
  • binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
  • x509 certificate: to encrypt SAML2 exchanges

By convention, when we generate the certificate here at Toucan we use the following commands :

$ BACKEND_DOMAIN="your-backend.toucantoco.com"
$ TECH_TEAM="the relevant name to the team"
$ ORGANISATION="your comany name"
$ EMAIL="your_email_for_important_stuff@domain.com"
$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /tmp/${BACKEND_DOMAIN}.key -out /tmp/${BACKEND_DOMAIN}.crt -subj "/emailAddress=${EMAIL}/C=FR/ST=France/L=Paris/O=${ORGANISATION}/OU=${TECH_TEAM}/CN=${BACKEND_DOMAIN}"

This is an example of a typical service provider block:

sp:
  entityId: 'https://<backend url>'
  assertionConsumerService:
    url: 'https://<backend url>/auth/provider/<provider id>/acs'
    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
  singleLogoutService:
    url: 'https://<backend url>/auth/provider/<providerd id>/sls'
    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
  NameIDFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified'
  x509cert: <...>

The metadata file to use when configuring your IdP will now be available here:

https://<backend url>/auth/provider/<provider id>/metadata

IdP configuration

In order to configure the IdP part, we need to set the values of:

  • entityId
  • singleSignOnService url
  • singleSignOnService binding
  • singleLogoutService url optional
  • singleLogoutService binding optional
  • x509 certificate

Other settings

  • user_provisioning: (bool) when set to false, users won’t be able to log in unless they already exist in Toucan Toco’s internal database. Default is true, which means that they will be created on-the-fly.
  • mapping:
  • nameId: (string) jq filter used to override the username of the connected user with a value found in the metadata sent by the IdP.
  • userTemplate: allows to override default rights of users who connect via the SSO (applied each time they log in)
  • privileges:
    • allSmallApps: an arrays of privileges (i.e. ['view'] or ['contribute']). By default, users won’t have any privilege (they will see an empty store).

This is an example usage:

user_provisioning: false  # users won't be created on-the-fly
mapping:
  nameId: '.Email'  # replace the username with the "Email" attribute
userTemplate:
  privileges:
    allSmallApps: ["view"]  # users will see all small apps

Example

Complete configuration of a SAML2 auth provider:

id: '<provider id>'
name: '<providr display name>'
type: 'SAML2'
icon: '<font awesome icon id>'
config:
  defaultRedirectUrl: '<frontend URL>'
  security:  # Optional, see https://github.com/onelogin/python-saml#settings for reference
    authnRequestsSigned: true
    signedMetaData: true
  sp:  # This part will be used to generate the metadata XML file
    entityId: 'https://<backend url>'
    assertionConsumerService:
      url: 'https://<backend url>/auth/provider/<provider id>/acs'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
    singleLogoutService:
      url: 'https://<backend url>/auth/provider/<provider id>/sls'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
    NameIDFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified'
    x509cert: '<...>'
  idp:  # This part is where you declare the information about the third party identity provider
    entityId: 'service entrypoint'
    singleSignOnService:
      url: '<sign on url>'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
    singleLogoutService:
      url: '<logout url>'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
    x509cert: '<...>'

SAML2 FAQ

Why do I get a “redirect_uri_mismatch” error after login?

→ most probably because you didn’t allow the URL in your app configuration on the identity provider

Do I need to care about the “authorized_origins” parameter on my identity provider server?

→ No. This parameter is only useful for full JavaScript applications.

OpenID Connect

A lot of services act as OpenID Connect identity providers (Google, Yahoo, Azure, etc.). In order to use one of them, you’ll typically have to follow the following setup steps:

  • on the identity provider server:
    • register your ToucanToco app (e.g. https://developers.google.com/identity/protocols/OpenIDConnect). If the registration is successful, you should be given a couple client_id, client_secret that will be used as credentials for you app,
    • configure your application permissions (e.g. what kind of information about an user your application will be allowed to ask for),
    • you’ll generally have to authorize the URL that the identity provider will call after a successful login. The exact name of the configuration field you’ll have to set will vary depending on your identity provider: for instance, on auth0, it’s called Allowed Callback URLs, on google, it’s called Allowed redirect URIs. The URL you have to set is always https://<backend-hostname>/auth/provider/<auth-provider-id>/auth_callback, for instance: https://api-myapp.toucantoco.com/auth/provider/google_openid_connect/callback
  • on the toucantoco application:
    • specify the identity provider information, as generally provided trough a specific endpoint (e.g. https://accounts.google.com/.well-known/openid-configuration) Those endpoints are sometimes discoverable automatically but we don’t support this feature for now,
    • specify your app’s credentials,
    • specify the front-end’s URL, the backend will need it to send you back to your application after the authenication process is over,
    • specify the required scopes (i.e. what information the Toucan will ask for). The default is ['openid', 'email'] if you don’t specify it but you can ask for anything the identity provider claims to support. Unsupported scopes will eventually be ignored anyway.
    • optionally, customize the userinfo. The default behavior is to perform a POST request and send the access_token in the message body. You can override this by specifying a userInfoRequest property and specify its method, behavior and headers subproperties. For instance: javascript   'userInfoRequest': { 'method': 'GET', 'behavior': {   'use_authorization_header': True, }, 'headers': {   'X-Whatever': 'hola',   }

Below is a template of configuration for an OpenID Connect authentication provider:

id: '[id]'
name: '[name]'
type: 'OIDC'
icon: '[icon]'
config:
  providerConfig:
      issuer: "https://accounts.google.com"
      authorization_endpoint: "https://accounts.google.com/o/oauth2/v2/auth"
      token_endpoint: "https://oauth2.googleapis.com/token"
      userinfo_endpoint: "https://openidconnect.googleapis.com/v1/userinfo"
      revocation_endpoint: "https://oauth2.googleapis.com/revoke"
      jwks_uri: "https://www.googleapis.com/oauth2/v3/certs"
      response_types_supported: [
          "code"
          "token"
          "id_token"
          "code token"
          "code id_token"
          "token id_token"
          "code token id_token"
          "none"
      ]
      subject_types_supported: [
          "public"
      ]
      id_token_signing_alg_values_supported: [
          "RS256"
      ]
      scopes_supported: [
          "openid"
          "email"
          "profile"
      ]
      token_endpoint_auth_methods_supported: [
          "client_secret_post"
          "client_secret_basic"
      ]
      claims_supported: [
          "aud"
          "email"
          "email_verified"
          "exp"
          "family_name"
          "given_name"
          "iat"
          "iss"
          "locale"
          "name"
          "picture"
          "sub"
      ]
      code_challenge_methods_supported: [
          "plain"
          "S256"
      ]
  defaultRedirectUrl: [front-end URL]
  appCredentials:
    client_id: 'your app-client id'
    client_secret: 'your app-secret id'
  userInfoRequest:
    method: "GET"
    behavior:
      use_authorization_header: true

Once again, providerConfig might seem huge and overwhelming but in most cases, it’s merely a copy and paste of the information given by your identity provider. You should not have to alter it.

After you updated the instance settings, the new auth provider will be listed on https://api-[name-of-toucan-branch].toucantoco.com/auth/providers and the Service Provider’s metadata are available to download at https://api-[name-of-toucan-branch].toucantoco.com/auth/provider/[name-of-your-idp].

User rights and permissions

User rights and permissions can be entirely controlled by a third party SSO IdP. This is configured first at the instance level (in instance settings) for users rights (control access to small app and assign groups) ; and second at each small app level for permissions (control access to reports and dashboard, or at row level).

User rights: authentication provider rules

When a user logs in using a SSO mechanism Toucan Toco can process it in order to give him relevant user rights and groups, based on the attributes sent by the IdP.

This can be configured thanks to “Authentication Provider Rules”, located at the bottom of the “Settings” page of your instance.

These rules are expressed as a python function that take a User object as input, and return a User object to be created or modified.

The relevant properties of the User object are:

  • attributes an object storing the user’s attributes as sent by the IdP
  • groups an array of strings for each of the user’s groups
  • privileges.samllApp which stores an object where each property is a small app id and the values are arrays of privileges i.e. ['view'] or ['contribute']

For example:

user = {
  'attributes': {  # this property contains all the attributes sent by the IdP
    'name': 'John Smith',
    'email': 'john@smith.example',
    'department': 'accounting',
    'region': 'France'
  },
  'groups': [],  # this is an array of the user's groups
  'privileges': {
    'smallApp': {  # this is an object where each property is a small app id and the value a type of permission
      'my_small_app_id': ['view']
    }
  }
}

With this we can imagine the following function:

def generate_rights(user):

    # we just create shortcuts to the relevant user's properties
    attributes = user['attributes']
    groups = user['groups']
    small_app_privileges = user['privileges']['smallApp']

    # here for example we assign a group to the user based on his region
    # (the group will be created if it does not allready exists)
    if 'region' in attributes:
        groups.append(attributes['region'])

    # give 'view' access to the small app  department
    # here for example we use a mapping from department names to small app ides
    department_small_apps_mapping = {
        'accounting': ''
    }
    # now we use the department sent by the IdP to select the relevant small app
    # and assign 'view' privileges.
    if 'department' in user['attributes']:
        small_app_privileges[
            department_small_apps_mapping[
                attributes['department']]] = ['view']

    return user

Permissions

Setup permissions

  • Use the PERMISSIONS_DATA field in the etl_config to make a data query that will return the user groups you want to create, and all the info you will need:
'PERMISSIONS_DATA':
  query:
    domain: 'user_groups'
  • Use the generate_permissions function within the permissions.py file to iterate over data returned by the ‘PERMISSIONS_DATA’ query (returns a list of dict). This function must return a list of dict containing the keys group (user group), and reports if your permission is about reports (mongo query acting as a mask over report queries).

Example:

def generate_permissions(data):
    """
    Insert here code to generate group specific permissions
    """

    user_groups_permissions = [{
        'group': "all",
        'reports': {}  # See everything
    }]
    for entity in data:
        user_groups_permissions.append({
            'group': entity[u'country'],
            'reports': {
                '$or': [{
                    'entityName': entity[u'country']
                }]
            }
        })

    return user_groups_permissions

You can also use permissions on dashboards and on data. If your permission targets data then you can also specify which domain is concerned by adding a field domain :

user_groups_permissions.append({
    'group': 'mygroup',
    'data': {'age': {'$gte': 18}},
    'domain': 'people'
})

Generate permissions

To generate permissions, do a POST request to /{small_app}/permissions/generate

Default report per user group

You can define a custom default report per user group. The dict appended to the permission list must contain a ‘default’ key, which contains a ‘report’ key, which is a dict containing a query to get the default report.

In this example, each user group will see all reports, but its default report will be the the one whose entityName is the same as the group’s name:

def generate_permissions(data):
    """
    Insert here code to generate group specific permissions
    """

    user_groups_permissions = [{
        'group': "all",
        'reports': {}  # See everything
    }]
    for entity in data:
        user_group_permissions.append({
            'group': entity['country'],
            'reports': {},  # See everything
            'default': {
                'reports': {
                    'entityName': entity['country']
                }
            }
        })

    return user_groups_permissions