3. Tokens

If you decide to implement new token classes, you need to change the configuration parameter TOKEN_MODULES. This parameter contains a list of token modules LinOTP should use.

Note

The module linotp.lib.tokenclass will always be used.

Starting with LinOTP 2.5.2 it is even simpler to add new token types to LinOTP. The complete token type handling is now done dynamically. This means you can

  • add a new token module

  • new policies that are necessary for this token

  • new management GUI (enrollment and token settings) and

  • new selfservice enrollment GUI

for this new token without having to adapt anything in the LinOTP core.

This is done by creating two files

new_token.py

This file contains all the program logic, the calculation and verification of the OTP values and the handling of the policies.

new_token.mako

This file contains the GUI parts for management and selfservice.

The existing tokens are located in the module linotp.lib.tokens.

Both files describe the new token and need to be located in the same directory i.e. the same python module.

3.1. The GUI file

The new_token.mako file is written in the MAKO templating language. More information on how MAKO works can be found on the project website [1].

Note

Please take a look at the example file dyntoken.mako.

To make the file handling easier there is only this single file providing the GUI for

  • selfservice

  • token configurations

  • token enrollment.

Therefore there is a top level switch in this file that evaluates the variable c.scope.

3.1.1. The scope config

config

The scope config is used for the generic token configuration information. E.g. for a time based HMAC Token (TOTP) you can use this scope to configure the default time window and the default OTP length. The scope config contains the complete HTML code of this config tab.

config.title

This is the header text of the config tab.

To read and write the token configuration to the LinOTP configuration database you need to define two javascript functions:

function dyn_get_config_val(){
       var ret = {};
   ret['DynTokenTimeOut']                    = 'dyn_challenge_timeout';
   ret['DynTokenMaxChallemges']      = 'dyn_max_challenge';
   return ret;
}

function dyn_get_config_params(){
   var ret ={}
   ret['DynTokenTimeOut']             = $('#dyn_challenge_timeout').val();
   ret['DynTokenMaxChallemges'] = $('#dyn_max_challenge').val();
   return ret
}

The name of the function starts with the token type name of the token. You already know tokens like hmac``or ``totp. So the function names concatenate like <token type>_get_config_val and <token type>_get_config_params(). The example above are the functions of the token type dyn.

In the config tab for the token, you might have several HTML object. The function maps the identifier of the HTML objects to the configuration keys stored in the LinOTP configuration database.

The function _get_config_val is called, when the config tab is opened. It tells LinOTP to get the config entry with the key DynTokenTimeOut from the LinOTP configuration and put it into the HTML object with the id=dyn_challenge_timeout.

The function _get_config_params is called, when the config tab is saved. The values of the HTML objects are written to the corresponding LinOTP config database entries.

Warning

All config entries are stored in one config table. So you need to review third party token modules and assure, that a foreign token modules does not interfere with your other tokens or break your LinOTP. Best practice is to let your token config key start with the token type as prefix.

3.1.2. The scope enroll

enroll

This is the HTML GUI for the enrollment of a token by the administrator. Here all necessary data input is displayed.

enroll.title

This is the header text for the enrollment.

In the enrollment process an administrator needs to add additional information, like the RADIUS server in case of a RADIUS Token, the secret key or whatever. These data need to be passed to LinOTP, therefore you need to define a javascript function <token type>_get_enroll_params. The function has to return a hash of all the parameters, that will be passed to the admin/init function:

function dyn_get_enroll_params(){
   var url = {}

   url['type'] = 'dyn';
   url['otppin'] = $('#dyn_pin1').val();
   url['hashlib'] = $('#dyn_algorith').val();
   url['otplen'] = $('#dyn_otplen').val();
   url['serial'] = create_serial('DYN');

   if  ( $('#dyn_key_cb').attr('checked') ) {
      url['genkey']='1';}
   else {
      url['otpkey'] = $('#dyn_key').val();}

  return url;
}

3.1.3. The scope selfservice

selfservice.enroll

This is the HTML GUI for the user self enrollment in the Selfservice Portal.

selfservice.title.enroll

This is the header text of the selfservice tab.

In the self enrollment process several information needs to be entered by the user and needs to be passed to LinOTP. Three javascript functions are used to read the data from the HTML GUI and pass it to LinOTP. The first function self_<token type>_get_param function is used to retrieve the hashed parameters that needs to be passed to the selfservice/userinit function:

function self_dyn_get_param()
{
   var urlparam = {};

   urlparam['type']          = 'dyn';
   urlparam['description'] = "self enrolled" ;
   urlparam['otpkey']                = $('#motp_secret').val();
   urlparam['otppin']                = $('#motp_s_pin1').val();
   urlparam['serial']                = generate_serial("LSMO");

   return urlparam;
}

Note

The generate_serial() function generates a new serial number with the passed prefix.

The function self_<token type>_clear is used to clear the input fields:

function self_dyn_clear()
{
   $('#motp_s_pin1').val('');
   $('#motp_secret').val('');
   $('#motp_s_pin2').val('');
}

Finally there is a function self_<token type>_submit that is called when the token is enrolled. This function can also be used to do a input validation:

function self_dyn_submit(){
   var ret = false

   if ($('#form_registermotp').valid()) {
          var params =  self_motp_get_param();
              enroll_token( params );
              //self_motp_clear();
              ret = true;
   } else {
          alert("Form data not valid.");
   }
   return ret;
}

Note

When doing input validation the corresponding jQuery.validator needs to be defined.

3.2. The token file

The token file dyntoken.py itself contains the token definition as a python class that inherits from TokenClass which is defined in linotp.lib.tokenclass.

The token class needs to provide a class method getClassInfo, that tells LinOTP, which scopes to use in which situation. Therefore it returns a dictionary of the following kind:

{
    'type'           : 'dyn',
    'title'          : 'my dyn Token',
    'description'    : ('my own dynamic otp token'),

    'init'         : {'page' : {'html'      : 'dyntoken.mako',
                                'scope'     : 'enroll',},
                    'title'  : {'html'      : 'dyntoken.mako',
                                'scope'     : 'enroll.tab',},
                        },

    'config'        : { 'page' : {'html'      : 'dyntoken.mako',
                                 'scope'      : 'config',},
                        'title'  : {'html'    : 'dyntoken.mako',
                                  'scope'     : 'config.tab',},
                      },

    'selfservice'   :  { 'enroll' : {'page' : {'html'  : 'dyntoken.mako',
                                               'scope' : 'selfservice.enroll',},
                                    'title' : { 'html' : 'dyntoken.mako',
                                              'scope'  : 'selfservice.tab.enroll',},
                                       },
                       },


    }

3.2.1. Challenge Response

Starting with LinOTP 2.6 you can easily create tokens capable of doing challenge response.

Usually a challenge will be triggered when sending an authentication request to /validate/check with the additional parameter data or challenge. The method is_challenge_request() determines, if the given request is a challenge or not.

The response will also be sent to the /validate/check interface. Usually the response has to contain the parameter state or transactionid so that the request will be identified as a response to a previous challenge. The method is_challenge_response() determines, if the given request is a response for a previous challenge.

Each token type can define it’s own challenge handling by overwriting the corresponding base class method.

So the methods is_challenge_request and is_challenge_response determine in which of the following three branches the request will be verified:

authenticate

This is the normal single shot authentication e.g. for push button OTP tokens. No challenge and no response is involved. is_challenge_request and is_challenge_response both return false. The authentication workflow will call the authenticate() method.

challenge

The method is_challenge_request returns true and is_challenge_response returns false. A new challenge will be created. The authentication workflow will call the method initChallenge() and createChallenge().

response

The method is_challenge_request returns false and is_challenge_response returns true. The authentication workflow will call the method checkResponse4Challenge().

Please see the base class definition in Base TokenClass.

3.2.2. Base TokenClass

See documentation for TokenClass class: linotp.lib.tokenclass.TokenClass