Extending OpenAM HOTP module to display OTP delivery details

OpenAM provide HOTP authentication module which can send OTP to user’s email address and/or telephone number. By default, OpenAM doesn’t displays user’s email address and/or telephone number while sending this OTP.

Solution

Versions used for this implementation: OpenAM 13.5, OpenDJ 3.5
One of the solution can include extending out of the box OpenAM’s HOTP module:
  • Extend HOTP auth module (openam-auth-hotp).
  • Update below property in extended amAuthHOTP.properties: send.success=Please enter your One Time Password sent at
  • Extend HOTPService appropriately to retrieve user profile details.
  • Change extended HOTP module code as per below (both for auto send and on request):

substituteHeader(START_STATE, bundle.getString("send.success") + <Get User contact details from HOTPService>);

Deploy

Register service and module (Note that for OpenAM v12 use amAuthHOTPExt-12.xml) :
$ ./ssoadm create-svc --adminid amadmin --password-file /tmp/pwd.txt --xmlfile ~/softwares/amAuthHOTPExt.xml
$ ./ssoadm register-auth-module --adminid amadmin --password-file /tmp/pwd.txt --authmodule com.sun.identity.authentication.modules.hotp.HOTPExt

UnRegister service and module (in case module needs to be uninstalled) : 
$ ./ssoadm unregister-auth-module --adminid amadmin --password-file /tmp/pwd.txt --authmodule com.sun.identity.authentication.modules.hotp.HOTPExt
$ ./ssoadm delete-svc --adminid amadmin --password-file /tmp/pwd.txt -s sunAMAuthHOTPExtService
  • Configure HOTPExt module with required SMTP server. Enable both SMS and Email.
  • Create a chain(otpChain) with (LDAP:Required, HOTPExt:Required). Set this chain as default for “Organization Authentication”
  • Restart OpenAM
  • Invoke HOTP module and appropriate message is displayed on screen with user’s email address and/or telephone number:

 

This blog post was first published @ theinfinitelooper.blogspot.com, included here with permission.

Invoking HOTP over REST

HOTP Introduction

An excellent article about the core OpenAM HOTP capability is: Use HOTP for two factor authentication

Building on the article, we show here a typical REST-based programmatic interaction when invoking HOTP from a client, such as a Portal.

Configuration in OpenAM

Setup HOTP as REQUISITE after DataStore under a realm called “levels”. You could choose not to use realms and create the authentication chain at the top level realm. However, you would modify the REST URL for all the ensuing calls.

Using PostMan, invoke a POST on the realm=/levels

Body-

Empty

Response-

{ "authId": "eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJqd3QiIH0.eyAib3RrIjogIm05ZWVpdjltNmsxdGRoN2dkdGM4MG5qdWFmIiwgInNlc3Npb25JZCI6ICJBUUlDNXdNMkxZNFNmY3hieHYzZjI0aXlzNDlEaG9NUlU3VENhMkU3dFdfcXY1Zy4qQUFKVFNRQUNNREVBQWxOTEFCUXROelU0TmpBME1UZzJNVFl5TWpnNU5EWXdOZy4uKiIsICJyZWFsbSI6ICJvPWxldmVscyxvdT1zZXJ2aWNlcyxkYz1vcGVuYW0sZGM9Zm9yZ2Vyb2NrLGRjPW9yZyIgfQ.SsOE-9DyAZ6apnj5SXaD9ED28T_RDFfpjV8hslHP99g", "template": "", "stage": "DataStore1", "callbacks": [ { "type": "NameCallback", "output": [ { "name": "prompt", "value": " User Name: " } ], "input": [ { "name": "IDToken1", "value": "" } ] }, { "type": "PasswordCallback", "output": [ { "name": "prompt", "value": " Password: " } ], "input": [ { "name": "IDToken2", "value": "" } ] } ] }

 

Add in the username and password as shown below:

Body (from the screenshot above)-

{ "authId": "eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJqd3QiIH0.eyAib3RrIjogIjl2Mm1lZGQ3NmpwdXViZGlncGkyZjFtNzk5IiwgInNlc3Npb25JZCI6ICJBUUlDNXdNMkxZNFNmY3d1amwzczR4TXdyMHI0S3paMFI2Qmk3LV9aTHMxcnRQby4qQUFKVFNRQUNNREVBQWxOTEFCTXlOelE1TmpJeU5qUTBOVFUwTlRreE5EQTMqIiwgInJlYWxtIjogIm89bGV2ZWxzLG91PXNlcnZpY2VzLGRjPW9wZW5hbSxkYz1mb3JnZXJvY2ssZGM9b3JnIiB9.WGemKm2u0O-vbhAkAClg0l0rndGEhDbhS3pncAsn6PA", "template": "", "stage": "DataStore1", "callbacks": [ { "type": "NameCallback", "output": [ { "name": "prompt", "value": " User Name: " } ], "input": [ { "name": "IDToken1", "value": "user.4" } ] }, { "type": "PasswordCallback", "output": [ { "name": "prompt", "value": " Password: " } ], "input": [ { "name": "IDToken2", "value": "Password2" } ] } ] }

 

Response

{ "authId": "eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJqd3QiIH0.eyAib3RrIjogIjl2Mm1lZGQ3NmpwdXViZGlncGkyZjFtNzk5IiwgInNlc3Npb25JZCI6ICJBUUlDNXdNMkxZNFNmY3d1amwzczR4TXdyMHI0S3paMFI2Qmk3LV9aTHMxcnRQby4qQUFKVFNRQUNNREVBQWxOTEFCTXlOelE1TmpJeU5qUTBOVFUwTlRreE5EQTMqIiwgInJlYWxtIjogIm89bGV2ZWxzLG91PXNlcnZpY2VzLGRjPW9wZW5hbSxkYz1mb3JnZXJvY2ssZGM9b3JnIiB9.WGemKm2u0O-vbhAkAClg0l0rndGEhDbhS3pncAsn6PA", "template": "", "stage": "HOTP2", "callbacks": [ { "type": "PasswordCallback", "output": [ { "name": "prompt", "value": " Enter OTP " } ], "input": [ { "name": "IDToken1", "value": "" } ] }, { "type": "ConfirmationCallback", "output": [ { "name": "prompt", "value": "" }, { "name": "messageType", "value": 0 }, { "name": "options", "value": [ " Submit OTP ", " Request OTP " ] }, { "name": "optionType", "value": -1 }, { "name": "defaultOption", "value": 0 } ], "input": [ { "name": "IDToken2", "value": 0 } ] } ] }

 

Set IDToken2 to value “1”, and re submit as a POST. OpenAM will generate the OTP code and send it to the user.

 

Body-

{ "authId": "eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJqd3QiIH0.eyAib3RrIjogImYwZW5xYTI2aHZla3Q0bG5tc24wbDFlbTI5IiwgInNlc3Npb25JZCI6ICJBUUlDNXdNMkxZNFNmY3k4cllnb2V3MDBWNkpOUW5rR1RjLVoyVjZmcjZFMS11US4qQUFKVFNRQUNNREVBQWxOTEFCTTRNak01TXpRMU9EazROekl4TlRnNU16UXkqIiwgInJlYWxtIjogIm89bGV2ZWxzLG91PXNlcnZpY2VzLGRjPW9wZW5hbSxkYz1mb3JnZXJvY2ssZGM9b3JnIiB9.Dfxj7bAtxHEenVPA_9t3iCFhu92zqk8lXAqxmQD1COU", "template": "", "stage": "HOTP2", "callbacks": [ { "type": "PasswordCallback", "output": [ { "name": "prompt", "value": " Enter OTP " } ], "input": [ { "name": "IDToken1", "value": "" } ] }, { "type": "ConfirmationCallback", "output": [ { "name": "prompt", "value": "" }, { "name": "messageType", "value": 0 }, { "name": "options", "value": [ " Submit OTP ", " Request OTP " ] }, { "name": "optionType", "value": -1 }, { "name": "defaultOption", "value": 0 } ], "input": [ { "name": "IDToken2", "value": 1 } ] } ] }

 

Response-

{ "authId": "eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJqd3QiIH0.eyAib3RrIjogImYwZW5xYTI2aHZla3Q0bG5tc24wbDFlbTI5IiwgInNlc3Npb25JZCI6ICJBUUlDNXdNMkxZNFNmY3k4cllnb2V3MDBWNkpOUW5rR1RjLVoyVjZmcjZFMS11US4qQUFKVFNRQUNNREVBQWxOTEFCTTRNak01TXpRMU9EazROekl4TlRnNU16UXkqIiwgInJlYWxtIjogIm89bGV2ZWxzLG91PXNlcnZpY2VzLGRjPW9wZW5hbSxkYz1mb3JnZXJvY2ssZGM9b3JnIiB9.Dfxj7bAtxHEenVPA_9t3iCFhu92zqk8lXAqxmQD1COU", "template": "", "stage": "HOTP2", "callbacks": [ { "type": "PasswordCallback", "output": [ { "name": "prompt", "value": " Enter OTP " } ], "input": [ { "name": "IDToken1", "value": "" } ] }, { "type": "ConfirmationCallback", "output": [ { "name": "prompt", "value": "" }, { "name": "messageType", "value": 0 }, { "name": "options", "value": [ " Submit OTP ", " Request OTP " ] }, { "name": "optionType", "value": -1 }, { "name": "defaultOption", "value": 0 } ], "input": [ { "name": "IDToken2", "value": 1 } ] } ] }

 

Supply OTP Code

Change IDToken2 to value “0” and this time have the user supply the OTP code s/he received:

 

Body-

{ "authId": "eyAiYWxnIjogIkhTMjU2IiwgInR5cCI6ICJqd3QiIH0.eyAib3RrIjogImYwZW5xYTI2aHZla3Q0bG5tc24wbDFlbTI5IiwgInNlc3Npb25JZCI6ICJBUUlDNXdNMkxZNFNmY3k4cllnb2V3MDBWNkpOUW5rR1RjLVoyVjZmcjZFMS11US4qQUFKVFNRQUNNREVBQWxOTEFCTTRNak01TXpRMU9EazROekl4TlRnNU16UXkqIiwgInJlYWxtIjogIm89bGV2ZWxzLG91PXNlcnZpY2VzLGRjPW9wZW5hbSxkYz1mb3JnZXJvY2ssZGM9b3JnIiB9.Dfxj7bAtxHEenVPA_9t3iCFhu92zqk8lXAqxmQD1COU", "template": "", "stage": "HOTP2", "callbacks": [ { "type": "PasswordCallback", "output": [ { "name": "prompt", "value": " Enter OTP " } ], "input": [ { "name": "IDToken1", "value": "86848280" } ] }, { "type": "ConfirmationCallback", "output": [ { "name": "prompt", "value": "" }, { "name": "messageType", "value": 0 }, { "name": "options", "value": [ " Submit OTP ", " Request OTP " ] }, { "name": "optionType", "value": -1}, { "name": "defaultOption", "value": 0 } ], "input": [ { "name": "IDToken2", "value": 0 } ] } ] }

 

At this time, if the OTP code was correct, the user will be logged into OpenAM.

The value of the iPlanetDirectoryPro cookie will be returned as tokenId.

 

Response-

{ "tokenId": "AQIC5wM2LY4Sfcyt-pO9V3hEMYK924cDC2_SlWLROXUBpsc.*AAJTSQACMDEAAlNLABQtMTcyMDQ4ODk5NTU0ODUzNTQ4MQ..*", "successUrl": "/openam/console" }

 

The iPlanetDirectoryPro can then be used to do other stuff, such as list user data values over REST: