Add authentication when delivering SMS over HTTP in YateSMSC

Add authentication when delivering SMS over HTTP

The aim of this document is to provide information about how to deliver a sms over HTTP and how to make extra actions before sending the actual request. In this case we’ll want to make an additional HTTP request to retrieve an authentication token and add it in subsequent requests.

Enabling functionality

For this, you will need to enable in your YateSMSC from YateMMI:

  •  a custom routing rule that delivers a SMS over HTTP – from YateMMI->Edit YateSMSC equipment->Routing step
  •  a JS script that catches the request to deliver over HTTP (from Scripts step) – from YateMMI->Edit YateSMSC equipment->Routing step

How it works

The routing rule instructs YateSMSC to route to an url starting with http:// or https://. This is done by the dispaching of a http.request message that is handled by the HTTP Client module.

A JS script will delay the http request sending the SMS (and maybe alter it later on) until it does other action (ex: can send one or more http requests before the request is actually sent).

Read also:

https://docs.yate.ro/wiki/HTTP_Client

https://docs.yate.ro/wiki/Javascript

 

Configuration

From YateMMI configure in your YateSMSC :

  • a custom routing rule – from YateMMI->Edit SMSC equipment->Routing step
  • an JS script (from Scripts step) – from YateMMI->Edit SMSC equipment->Routing step

Example rule:

[default]

^9631234.*$=http://127.0.0.1/simulate_sms_service.php;json_body=true;http_use_auth=auth_service_name;copyparams=http_use_auth;

http_method=POST

Where:

* SMSs sent to numbers starting with 9631234 will be routed over HTTP by making a POST request to http://127.0.0.1/simulate_sms_service.php.

* json_body,http_use_auth,copyparams,http_method are parameters that will be added to call.route message

* json_body=true  makes http request with content type ‘application/json’

* copyparams=http_use_auth is used to instruct that use_auth must be copied to http.request message

* http_method specifies if it should be a GET or POST request; default is GET

The JS script must catch the http.request message before it is handled by our HTTP Client module. (priority<90)

Uses of catching HTTP request

Because after the call.route with url starting with http:// or https://, an http.request message is dispatched, interested parties can catch the message and do some decisions based on that.

Handling http.request messages

Below there is an example of script that:

  • lets the http.request pass to HTTP Client module if no use_auth param is found in http.request message
  • if use_auth is set then it assumes it needs to add authentication information to the http request
  • delays the handling for http.request message until it sends the authentication request

// helper function for dispatching http.request message

// use sync=true if you need to use the request response

function customHttpRequest(url,sync,accept,oneline,extra,body,return_mess)

{

    var m = new Message(‘http.request’,false,extra);

    m.url = url;

    m.method = ‘post’;

    m.type = ‘application/x-www-form-urlencoded/text’;

    m.accept = accept;

    m.multiline = !oneline;

    m.body = body;

    if (!sync) {

        m.enqueue();

        return null;

    }

    m.wait = true;

    if (!m.dispatch(true))

        return null;

    if (m.error)

        return null;

    if (!!return_mess)

        return m;

    return ” + m.retValue();

};

function onHttpModifier(msg)

{

    // if message has use_auth empty,

    // let message pass to http client module, by returning false

    var acc = msg.use_auth;

    if (!acc)

        return false;

    // if value set in use_auth is not a key in auth_info global object(initialized bellow),

    // let message pass to http client module, by returning false

    var acc_info = auth_info[acc];

    if (!acc_info) {

         Engine.debug(Engine.DebugWarn, ‘Unknown auth account \” + acc + ‘\”);

         return false;

    }

    //verify if auth token expired

    var expired = false;

    if (acc_info[‘expires_in’] && acc_info[‘expires_in’]<Date.now())

        expired = true;

    // retrive auth token and it’s expiration time

    // * if it expired or

    // * if we are not already authenticated

    if (!acc_info[‘key’] || expired) {

        //in our case the body must have next format:         

        //username=${u}&password=${p}&grant_type=password

        body = ‘username=’ + acc_info.username + ‘&password=’ + acc_info.password + ‘&grant_type=’ + acc_info.grant_type;

        //make the extra http request to the corresponding url

        //autz variable will contain the body from the response

        autz = customHttpRequest(acc_info.url, true, ‘application/json’, false, acc_info, body);

        if (!autz) {

            Engine.debug(Engine.DebugWarn, ‘Authentication to \” + acc_info[‘url’] + ‘\’ failed.’);

            msg.reason = ‘failed auth’;

            return false;

            }

        //decode response (in our case the response has JSON format)

        var autz_decoded = JSON.parse(autz);

        if (undefined===autz_decoded) {

            Engine.debug(Engine.DebugWarn, ‘Failed decoding JSON response \” + autz + ‘\”);

            msg.reason = ‘failed autz decoding’;

            return false;

            }

        //add the key and the expiration time in auth_info global array

        acc_info[‘key’] = autz_decoded[‘access_token’];

        //expires_in param is in seconds and represents the token’s lifetime

        //transform it to miliseconds multipling with 1000 and add it to current timestamp to obtain the expiration time

        acc_info[‘expires_in’] = Date.now() + autz_decoded[‘expires_in’]*1000;

        Engine.debug(Engine.DebugInfo, ‘Got autz \” + autz + ‘\”);

    }

    //add the authorization token to http headers

    msg.http_Authorization = ‘Bearer ‘ + acc_info[‘key’];

    msg.accept = ‘application/json’;

    var body = JSON.parse(msg.body);

    if (undefined===body)

        Engine.debug(‘Can not parse json body \” + body + ‘\”);

    var new_body = {‘mobile’:body.dest, ‘message’:body.text, ‘senderId’:body.orig};

    msg.body = JSON.stringify(new_body);

    msg.redirect = 1;

}

//a global variable that may hold the url, token, params to auth service for one or multiple sms services

// format:

// auth_info = {

//     “service_name”: {

//         username:’the_username’,

//         password:’the_passwd’,

//         grant_type:’the_grant_type’,

//         url:’http(s)://the_url’,

//         key:’the_token’,

//         expires:’the_expiration_unix_timestamp’

//     }

// }

auth_info = { ‘auth_service_name‘:{ username:’my_username’, password:’my_password’, grant_type:’password’, url:’https://127.0.0.1 ‘ } };

// install onHttpModifier handler for http.request messages

// http client module uses priority 90, so use priority 80 to catch it first

Message.install(onHttpModifier,’http.request’,80);

The above example script, catches the http.request message before HTTP Client module, and passes it as an argument to onHttpModifier() handler function.

 

After onHttpModifier alters the http.request message and sends an extra http request(if necessary), message will pass to the HTTP Client module.

The HTTP Client module will make the actual request corresponding to http.request message.

 

Inside onHttpModifier, if http.request has use_auth property:

* depending on use_auth value

–> a POST request will be made to the corresponding url from auth_info global object to the corresponding sms service url

–> the request will have next body format: ‘username=${username}&password=${password}&grant_type=${grant_type}

* the http.request message is altered in order to:

–> add Authorization header to the actual http request

–> set header accept to application/json

–> Enable HTTP 3xx redirects and set max number of allowed redirects to one

–> set http request body

Example of http.request message being dispatched after call.route message returns an url starting with http:// or https://

Sniffed ‘call.route’ time=1605001996.394041                                                                                                                                           

thread=0x1ecf6b0 ‘Engine Worker’  

  data=(nil)

  retval='(null)’

  param[‘route_type’] = ‘msg’

  param[‘module’] = ‘smsc_map’

  param[‘billid’] = ‘1604937235-3’

  param[‘retries’] = ‘4’

  param[‘caller’] = ‘Andreea’

  param[‘callernumplan’] = ‘unknown’

  param[‘callernumtype’] = ‘alphanumeric’

  param[‘called’] = ‘963123456789′

  param[‘callednumplan’] = ‘isdn’

  param[‘callednumtype’] = ‘international’

  param[‘submit_info’] = ‘HTTP 127.0.0.1:37032’

  param[‘sms_type’] = ‘deliver’

  param[‘sms_more’] = ‘false’

  param[‘sms_pid’] = ‘0’

  param[‘sms_dcs’] = ‘0’

  param[‘sms_udh’] = ‘false’

  param[‘sms_udl’] = ‘4’

  param[‘sms_scts’] = ‘1605001995’

 Returned true ‘call.route’ delay=0.001525                                                                                                                                                             

 thread=0x1ecf6b0 ‘Engine Worker’

  data=(nil)

  retval=’http://127.0.0.1/simulate_sms_service.php

  param[‘route_type’] = ‘msg’

  param[‘module’] = ‘smsc_map’

  param[‘billid’] = ‘1604937235-3’

  param[‘retries’] = ‘4’

  param[‘caller’] = ‘Andreea’

  param[‘callernumplan’] = ‘unknown’

  param[‘callernumtype’] = ‘alphanumeric’

  param[‘called’] = ‘963123456789′

  param[‘callednumplan’] = ‘isdn’

  param[‘callednumtype’] = ‘international’

  param[‘submit_info’] = ‘HTTP 127.0.0.1:37032’

  param[‘sms_type’] = ‘deliver’

  param[‘sms_more’] = ‘false’

  param[‘sms_pid’] = ‘0’

  param[‘sms_dcs’] = ‘0’

  param[‘sms_udh’] = ‘false’

  param[‘sms_udl’] = ‘4’

  param[‘sms_scts’] = ‘1605001995’

  param[‘handlers’] = ‘javascript:15,sip:100,regexroute:100’

  param[‘json_body’] = ‘true’

  param[‘http_use_auth‘] = auth_service_name‘                                                                                                                                                  

  param[‘copyparams’] = ‘http_use_auth‘                                                                                                                                                                

  param[‘http_method’] = ‘POST’                                                                                                                                                                            

Sniffed ‘http.request’ time=1605001996.396972                                                                                                                                                      

thread=0x1ecf6b0 ‘Engine Worker’                                                                                                                                                                        

  data=(nil)                                                                                                                                                                                                            

  retval='(null)’                                                                                                                                                                                                        

  param[‘body’] = ‘{“more”:false,”udhi”:false,”type”:”deliver”,”orig”:”Andreea”,”pid”:0,”dcs”:0,”scts”:1605001995,”udl”:4,”ud”:”f4f29c0e”,”udh”:false,

“dest”:963123456789,”tpdu”:”040dd04137595e2e870100000211011135518004f4f29c0e”,”data”:

“f4f29c0e”,”text”:”test”,”http_use_auth“:”auth_service_name“}’

  param[‘type’] = ‘application/json’                                                                                                                                                                         

  param[‘use_auth‘] = ‘auth_service_name‘    

  param[‘method’] = ‘POST’            

  param[‘url’] = ‘http://127.0.0.1/simulate_sms_service.php‘  

  param[‘accept’] = ‘text/plain;charset=UTF-8’                  

  param[‘multiline’] = ‘false’                                    

  param[‘agent’] = ‘YATE/6.2.1 (yate-smsc)’               

  param[‘wait’] = ‘true’                                                       

Returned true ‘http.request’ delay=0.001434     

thread=0x1ecf6b0 ‘Engine Worker’        

data=(nil)                                           

retval=’test response from this request’            

param[‘body’] = ‘{“mobile”:963123456789,”message”:”test”,”senderId”:”Andreea”}’   

param[‘type’] = ‘application/json’                                                     

param[‘use_auth‘] = ‘auth_service_name‘            

param[‘method’] = ‘POST’             

param[‘url’] = ‘http://127.0.0.1 /simulate_sms_service.php’  

param[‘accept’] = ‘application/json’             

param[‘multiline’] = ‘false’    

param[‘agent’] = ‘YATE/6.2.1 (yate-smsc)’   

param[‘wait’] = ‘true’               

param[‘handlers’] = ‘javascript:80,httpclient:90’          

param[‘http_Authorization’] = ‘Bearer 916kec3jf4g7850bidah2’      

param[‘redirect’] = ‘1’             

param[‘xversion_http’] = ‘1.1’        

param[‘code’] = ‘200’         

param[‘code_text’] = ‘OK’                        

param[‘xcontent_length’] = ‘307’          

param[‘xtype’] = ‘text/html; charset=UTF-8’

Example of sms routed to HTTP Client module and handled by JS script before sending the actual http.request

Sniffed ‘call.route’ time=1605010509.235138      

thread=0x1ecf480 ‘Engine Worker’      

data=(nil)                  

retval='(null)’

param[‘route_type’] = ‘msg’                          

param[‘module’] = ‘smsc_map’

param[‘billid’] = ‘1604937235-4’

param[‘retries’] = ‘4’

param[‘caller’] = ‘Andreea’

param[‘callernumplan’] = ‘unknown’

param[‘callernumtype’] = ‘alphanumeric’

param[‘called’] = ‘963123456789′

param[‘callednumplan’] = ‘isdn’

param[‘callednumtype’] = ‘international’

param[‘submit_info’] = ‘HTTP 127.0.0.1:47962’

param[‘sms_type’] = ‘deliver’

param[‘sms_more’] = ‘false’

param[‘sms_pid’] = ‘0’

param[‘sms_dcs’] = ‘0’

param[‘sms_udh’] = ‘false’

param[‘sms_udl’] = ‘4’

param[‘sms_scts’] = ‘1605010508’

Returned true ‘call.route’ delay=0.001448

thread=0x1ecf480 ‘Engine Worker’

data=(nil)

retval=’http://127.0.0.1/simulate_sms_service.php

param[‘route_type’] = ‘msg’

param[‘module’] = ‘smsc_map’

param[‘billid’] = ‘1604937235-4’

param[‘retries’] = ‘4’

param[‘caller’] = ‘Andreea’

param[‘callernumplan’] = ‘unknown’

param[‘callernumtype’] = ‘alphanumeric’

param[‘called’] = ‘963123456789′

param[‘callednumplan’] = ‘isdn’

param[‘callednumtype’] = ‘international’

param[‘submit_info’] = ‘HTTP 127.0.0.1:47962’

param[‘sms_type’] = ‘deliver’

param[‘sms_more’] = ‘false’

param[‘sms_pid’] = ‘0’

param[‘sms_dcs’] = ‘0’

param[‘sms_udh’] = ‘false’

param[‘sms_udl’] = ‘4’

param[‘sms_scts’] = ‘1605010508’

param[‘handlers’] = ‘javascript:15,sip:100,regexroute:100’

param[‘json_body’] = ‘true’

param[‘http_use_auth‘] = ‘auth_service_name

param[‘copyparams’] = ‘http_use_auth

param[‘http_method’] = ‘POST’

Sniffed ‘http.request’ time=1605010509.238623

thread=0x1ecf480 ‘Engine Worker’

data=(nil)

retval='(null)’

param[‘body’] = ‘{“more”:false,”udhi”:false,”type”:”deliver”,”orig”:”Andreea”,”pid”:0,”dcs”:0,”scts”:1605010508,”udl”:4,

“ud”:”f4f29c0e”,”udh”:false,”dest”:963123456789,”tpdu”:”040dd04137595e2e870100000211014151808004f4f29c0e”,”data”:

“f4f29c0e”,”text”:”test”,”http_use_auth“:”auth_service_name“}’

param[‘type’] = ‘application/json’

param[‘use_auth‘] = ‘auth_service_name

param[‘method’] = ‘POST’

param[‘url’] = ‘http://127.0.0.1/simulate_sms_service.php

param[‘accept’] = ‘text/plain;charset=UTF-8’

param[‘multiline’] = ‘false’

param[‘agent’] = ‘YATE/6.2.1 (yate-smsc)’

param[‘wait’] = ‘true’

Sniffed ‘http.request’ time=1605010509.240414

thread=0x1ecf480 ‘Engine Worker’

data=(nil)

retval='(null)’

param[‘username’] = ‘my_username’

param[‘password’] = (hidden)

param[‘grant_type’] = ‘password’

param[‘url’] = ‘http://127.0.0.1/simulate_auth_service.php&#8217;

param[‘key’] = ‘916kec3jf4g7850bidah2’

param[‘expires_in’] = ‘1605002017889’

param[‘method’] = ‘post’

param[‘type’] = ‘application/x-www-form-urlencoded/text’

param[‘accept’] = ‘application/json’

param[‘multiline’] = ‘true’

param[‘body’] = ‘username=my_username&password=mypassword&grant_type=password’

param[‘wait’] = ‘true’

Returned true ‘http.request’ delay=0.001927

thread=0x1ecf480 ‘Engine Worker’

data=(nil)

retval='{“access_token”:”b4e3c1ki962jghad0f587″,”token_type”:”bearer”,”expires_in”:30,”userName”:”my_username”,

“.issued”:”Tue, 10 Nov 2020 12:15:09 GMT”,”.expires”:”Tue, 10 Nov 2020 12:15:39 GMT”}’

param[‘username’] = ‘my_username’

param[‘password’] = (hidden)

param[‘grant_type’] = ‘password’

param[‘url’] = ‘http://127.0.0.1/simulate_auth_service.php&#8217;

param[‘key’] = ‘916kec3jf4g7850bidah2’

param[‘expires_in’] = ‘1605002017889’

param[‘method’] = ‘post’

param[‘type’] = ‘application/x-www-form-urlencoded/text’

param[‘accept’] = ‘application/json’

param[‘multiline’] = ‘true’

param[‘body’] = ‘username=my_username&password=my_password&grant_type=password’

param[‘wait’] = ‘true’

param[‘handlers’] = ‘javascript:80,httpclient:90’

param[‘xversion_http’] = ‘1.1’

param[‘code’] = ‘200’

param[‘code_text’] = ‘OK’

param[‘xcontent_length’] = ‘184’

param[‘xtype’] = ‘text/html; charset=UTF-8’

Returned true ‘http.request’ delay=0.005290

thread=0x1ecf480 ‘Engine Worker’

data=(nil)retval=’test response for this request’

param[‘body’] = ‘{“mobile”:963123456789,”message”:”test”,”senderId”:”Andreea”}’

param[‘type’] = ‘application/json’

param[‘use_auth‘] = ‘auth_service_name

param[‘method’] = ‘POST’

param[‘url’] = ‘http://127.0.0.1/simulate_sms_service.php

param[‘accept’] = ‘application/json’

param[‘multiline’] = ‘false’

param[‘agent’] = ‘YATE/6.2.1 (yate-smsc)’

param[‘wait’] = ‘true’

param[‘handlers’] = ‘javascript:80,httpclient:90’

param[‘http_Authorization’] = ‘Bearer b4e3c1ki962jghad0f587’

param[‘redirect’] = ‘1’

param[‘xversion_http’] = ‘1.1’

param[‘code’] = ‘200’

param[‘code_text’] = ‘OK’

param[‘xcontent_length’] = ‘307’

param[‘xtype’] = ‘text/html; charset=UTF-8’