For each authentication, it is possible to try to generate a temporary password and send it by email or SMS to the specified user in the login form. This is known as One Time Password (OTP).
OTP aims to provide an easy way to authenticate in applications and help users log in without using a password so that it is not necessary to remember it or use password managers.
Its purpose is to get around the reluctance to have a password and memorize it. OTP gives the possibility to have users authenticated with random passwords in each login process. This allows users to access the application in a safe way, by only remembering their nick/email.
When a user authenticates in a web app, a password is generated and an email/SMS is sent with the generated password. By default, GAM sends passwords by email; however, you can customize how codes are sent to users. For example, you can send the passwords by SMS.
The same steps apply to mobile applications.
The self-generated password must not be expired and can be used for a single login. For security reasons, OTP must have an expiration date. The expiration time can be set by the application administrator.
To allow a user to use OTP, the user must exist in GeneXus Access Manager (GAM). The user must be previously registered, and the authenticity of the method used to receive the OTP should have been validated – either the email or the SMS to verify that it is actually from the registered user. This means that you cannot create new users through OTP.
A user who logs in with OTP is impersonating a user that already exists in GAM with some other authentication type.
One Time Password is an authentication type, so to implement it you have to add it as another authentication type.
The following example shows the minimum requirements for OTP implementation.
- Step 1 - In the GAM menu, select SETTINGS > AUTHENTICATION TYPES.

- Step 2 - Click on the ADD option.

- Step 3 - In the Combo Box, select One Time Password. After this, a form is going to be shown.

- Step 4 - In the form, you must give a Name, select the "Enabled?" checkbox, and select the "Use For First Factor Authentication" checkbox (shown as #2 in the image).

The following table describes the OTP properties. The number that precedes each property corresponds to the number shown in the image above.
Property
|
Description
|
1- Impersonate
|
Authentication type where users are going to be validated when using OTP. Users must already exist.
|
2- UseForFirstFactorAuthentication
|
Sets whether this login method is used as the first-factor authentication. If you don't set this property, OTP could only be used as a second factor.
|
3- EventValidUser
GAMEvents.User_OneTimePasswordValidateUser
|
Developer event for validating that the user who asked for an OTP is allowed to do it.
|
4- AutogeneratedCodeLength
|
Auto-generated key length (between 3 and 32).
Default = 6.
|
5- GenerateCodeOnlyNumbers
|
Only numeric auto-generated key.
Default = True.
|
6- CodeExpirationTimeout
|
OTP key lifetime (in seconds).
Default = 1800.
Minimum value = 10.
|
7- MaximumDailyNumberCodes
|
Maximum number of daily OTPs.
0 = unlimited.
Default = 12.
|
8- NumberUnsuccessfulRetriesToLockOTP
|
Number of failed retries to lock the sending of OTPs to the user.
0 = never.
Default = 3.
|
9-NumberUnsuccessfulRetriesToBlockUserBasedOfOTPLocks
|
Number of failed retries to block the user.
|
10- AutoUnlockTime
GAMUser.UnblockOTPCodes(&Errors)
|
Time to unlock the user OTP (in minutes)
Default = 60.
If 0 is set, the user keeps locked until an administrator unlocks the user.
|
11-EventSendCode
GAMEvents.User_OneTimePasswordSendCode
|
Developer event to send the OTP key.
Empty property value means that GAM will send OTP by email.
|
12-Empty property EventSendCode
|
Empty property value means that GAM will send OTP by email. Also, the “GAM - Email” option will be displayed and a combo box containing "GAMEvents.User_OneTimePasswordSendCode" GAM Event will be listed.
|
13-MailMessageSubject
(Only if EventSendCode is empty)
|
Subject of the email that the user will receive. In the subject, “%1” is a way to indicate that the app name is going to be in that place.
|
14-MailMessageBodyHTML
(Only if EventSendCode is empty)
|
Message sent to the user (Allows HTML code).
If it contains “%1”, the name of the app will be sent in that place.
“%2” is the place where the OTPCode will be located.
Just writing “%1” will only send the OTPCode.
If neither “%1” nor “%2” is written, the OTP code will be at the end of the message.
|
15-EventValidateCode
GAMEvents.User_OneTimePasswordValidateCode
|
Developer event to validate the OTPCode.
It is a combo box with the first option as “GAM”, then a list of GAM events of GAMEvents.User_OneTimePasswordValidateCode type.
If nothing is set, GAM will validate OTP with the key stored in the database table.
If there is a proc selected, a GAM event will be executed.
|
Some events of the OTP behavior can be customized. You don't have to customize all the events, so if you have to change one of the four events' behavior, you can do it. The events are as follows:
- User validation. Number 3 in the table above.
- Code generation.
- Code sending. Number 11 in the table above.
- Code validation. Number 15 in the table above.
In order to implement these events, you have to subscribe to them in the GAM backend.
- Step 1: You have to subscribe to each of the events that you want to be related to the OTP custom action.
The following image shows an example of the GAM Events Subscription.

- Step 2: Add a One Time Password authentication type, and select "Code Generation Type" = OTP Custom.
In the following image, it is number 1.

- Step 3: Choose the events that you want to be customized. In the image, numbers 2,3,4,5 are the inputs that belong to the customizable events.
As you can see, the names of the contents of those inputs are the names of the events that were added in Step 1.
This section describes the different GAM events that you can subscribe to.
For each event, the receiving object is GAMOTPEventSubscription.
GAMOTPEventSubscription:
{
"ApplicationId": 2
"UserGUID": "3f471dce-cbcc-4467-a269-77f39a4132d7"
"OTPCode" : ""
}
- In case of a successful event
GAMOTPEventSubscription:
{
"ApplicationId": 2
"UserGUID": "3f471dce-cbcc-4467-a269-77f39a4132d7"
"OTPCode" : "123456"
}
- In case of an unsuccessful event
GAMError:
{
"Code": 440,
"Message": "OTP custom event error when generating code"
}
If the GeneXus developer builds a proc to customize this action, the event proc must have: Parm(in:&EventName, in:&JsonIN, out:&JsonOUT);
This event gets in the &JsonIN a JSON from the GAMOTPEventSubscription object containing the ApplicationID and UserGUID. If there is an error, a JSON with the GAMError format object must be retrieved from &JsonOUT.
On the other hand, if the response is correct, &JsonOUT will retrieve nothing.
if the GeneXus developer builds a procedure to customize the password generation process, the event procedure will have: Parm(in:&EventName, in:&JsonIN, out:&JsonOUT);
This event receives &JsonIN with a JSON of the GAMOTPEventSubscription object with ApplicationID and the UserGUID.
If there is an error, &JsonOUT will retrieve a JSON with GAMError format. On the other hand, if there is no error the &JsonOUT will retrieve a JSON with the 3 attributes of GAMOTPEventSubscription object, and with data in the OTPCode field.
This event only allows customizing the way that the OTPCode is sent to the user (SMS, email, etc).
An email server is required if GAM Email send is set. This configuration can be done by going to “Repository Configuration” > “Email”.
If the GeneXus developer builds a proc to customize this action, the event proc must have Parm(in:&EventName, in:&JsonIN, out:&JsonOUT). This event gets in the &JsonIN a JSON from the GAMOTPEventSubscription object containing the ApplicationID, UserGUID, and OTPCode. If there is an error, from &JsonOUT a JSON must be retrieved with the GAMError format object.
On the other hand, if the response is correct, &JsonOUT will retrieve nothing.
If nothing is set, GAM will validate OTP with the key stored in its database table.
For the cases where the GeneXus user develops a proc to customize something, if the event selected is executed, the proc will also be executed.
If the GeneXus developer builds a proc to customize this action, the event proc must have Parm(in:&EventName, in:&JsonIN, out:&JsonOUT). This event gets in the &JsonIN a JSON from the GAMOTPEventSubscription object containing the ApplicationID, UserGUID, and OTPCode. If there is an error, from &JsonOUT a JSON will be retrieved with the GAMError format object. On the other hand, if there is no error, nothing will be retrieved.
This event only allows customizing the OTPCode validation by the developer; once this validation is done correctly, GAM will also validate it.
The following code is an example of a procedure that was set to trigger in custom OTP while the code is being validated.
Case &EventName = GAMEvents.User_OneTimePasswordValidateCode
&GAMOTPEventSubscription.FromJsonString(&JsonIN)
Msg("TRACE-GAMEventTest - User_OneTimePasswordValidateCode - &GAMOTPEventSubscription:" + &GAMOTPEventSubscription.ToJsonString() ,status)
&GAMUser.Load(&GAMOTPEventSubscription.UserGUID)
If &GAMUser.Success()
If &GAMUser.EMail.Trim() = !"user@example.com"
If not &GAMOTPEventSubscription.OTPCode.IsEmpty()
If &GAMOTPEventSubscription.OTPCode = "123456"
//Success validation !!!
Msg("TRACE-GAMEventTest - User_OneTimePasswordValidateCode - OK1:" + &GAMOTPEventSubscription.OTPCode + " Email:" + &GAMUser.EMail ,status)
Else
&GAMError.Code = GAMErrorMessages.UserInactive
&GAMError.Message = "the code is not valid. try again. THIS IS A CUSTOM MESSAGE :)"
&JsonOUT = &GAMError.ToJsonString()
Endif
Else
&GAMError.Code = GAMErrorMessages.UserInactive
&GAMError.Message = "Error validating code. could not get the code."
&JsonOUT = &GAMError.ToJsonString()
Endif
Else
//Code validation ok !!!
Msg("TRACE-GAMEventTest - User_OneTimePasswordValidateCode - OK2:" + &GAMOTPEventSubscription.OTPCode + " Email:" + &GAMUser.EMail ,status)
Endif
Else
&GAMError.Code = GAMErrorMessages.UserInactive
&GAMError.Message = "User does not exists, verify user name."
&JsonOUT = &GAMError.ToJsonString()
Endif
EndCase
This method resets the number of OTP blocks, failed OTP retries, and the number of daily OTPs.
Here you can see how to set an SMTP server with Google. Many of the configurations that you will see in the example below were done based on Google SMTP server.
- Step 1 - To add a new email server, go to SETTINGS > REPOSITORY CONFIGURATION > Email.
- Step 2 - The required properties to set the email server are:
-
Email server host
-
Email server port
-
Email server secure
-
Email server use authentication
-
Email server authentication User name
-
Email server authentication User password

The name of the application is set in SETTINGS > REPOSITORY CONFIGURATION.
Here, there is a form with an input "Name". It refers to the application name.

The following code represents how OTP is implemented in the web app login.
Event 'GAM-OneTimePassword'
&AdditionalParameter.AuthenticationTypeName = !"gam-otp-web"
&AdditionalParameter.OTPStep = 1
&LoginOK = GAMRepository.Login(&UserName, &UserPassword_nula, &AdditionalParameter, &Errors )
If &LoginOK
&URL = GAMRepository.GetLastErrorsURL()
If &URL.IsEmpty()
GAMHome()
Else
Link(&URL)
Endif
Else
If &Errors.Count > 0
If (&Errors.Item(1).Code = GAMErrorMessages.UserAccessCodeSent) //Error 400
&UserName.Enable = False
&UserOTP.Enable = True
Login.Caption = "Send Access Code"
Endif
Endif
Endif
Endevent
If you just want to have the OTP authentication, the code of the WebPanel will look as shown below:
Event 'GAM-OneTimePassword-Step2'
&AdditionalParameter.AuthenticationTypeName = !"gam-otp-web"
&AdditionalParameter.OTPStep = 2
&LoginOK = GAMRepository.Login(&UserName, &User_OTP, &AdditionalParameter, &Errors )
Endevent
The following code represents how OTP is implemented in Native Mobile applications login.
Event 'GAM-OneTimePassword-Step1'
Composite
&LoginExternalAdditionalParameters.AuthenticationTypeName = !"gam-otp-sd"
&LoginExternalAdditionalParameters.OTPStep = 1
GeneXus.SD.Actions.LoginExternal(GAMAuthenticationTypes.GAMOTP, &UserName, &Password_nula, &LoginExternalAdditionalParameters)
EndComposite
Endevent
The OTP authentication through OAuth, will consist of two steps, the first one (otp_step=1) in which the OTP code is received, and the second one (otp_step=2) in which the obtained code is used to request the token.
The Endpoint is: https://<domain>/<virtual_directory>/oauth/gam/v2.0/access_token
FIRST POST
Body:
client_id: Client ID of the application, required.
client_secret: Client Secret of the application, required.
grant_type=password: It's required.
username: Username of the user with OTP enabled, required.
authentication_type_name: Name of the OTP authentication type configured at the server, required.
otp_step=1: It's required.
POSTMAN Example

Response: You will receive a http error "401 Error Not Authorized" and the "GAM Error 400" at the body.
{
"Code": 400,
"Message": "User access code was sent."
}
SECOND POST
Body:
client_id: Client ID of the application, required.
client_secret: Client Secret of the application, required.
grant_type=password: It's required.
password: OTP Code requested in the FIRST POST, required.
username: Username of the user with OTP enabled, required.
authentication_type_name: Name of the OTP authentication type configured at the server, required.
otp_step=2: It's required.

Response:
{
"access_token": "ae47229f-e133-42d1-87e0-c5ac59e51edf!GGpbRLk093GFVAzW7Pbae5yVDgymVvRaxthQS1ajkpgU3Ur2xDqSjsJ9q7XZpJMMGlTe7KmZBsoqC4",
"token_type": "Bearer",
"expires_in": 0,
"refresh_token": "",
"scope": "gam_user_data",
"user_guid": "4c921af0-6805-4f57-9efe-4d6cd6821e05"
}
Important:
The scopes received depend on the scopes that the application has enabled to share. In this case the application with "client_id=0c6fe15b76f14f5b8a0805c1b6c20-IDP" has enabled to share only gam_user_data.
Check possible Scopes: GAM - OAuth User Scopes
In turn, the developer can choose that the application does not share any User Information or the User's GUID, for this the following property in the GAM Backoffice is available "Do not share user IDs: True", or using the GAM API "&GAMApplication.ClientDoNotShareUserIDs = True".
The Endpoint is: https://<domain>/<virtual_directory>/oauth/gam/v2.0/userinfo
GET
Headers
Content-Type: Type of content that will be returned. Use application/x-www-form-urlencoded, required.
Authorization: access_token obtained in the SECOND POST, required.

Response:
{
"guid": "4c921af0-6805-4f51-9cfe-4d6cd6821e05",
"username": "juan",
"email": "jperez@gmail.com",
"verified_email": true,
"first_name": "Juan",
"last_name": "Perez",
"external_id": "",
"gender": "M",
"url_image": "",
"url_profile": "",
"phone": "099 888 888",
"address": "",
"city": "",
"state": "",
"post_code": "",
"language": "",
"timezone": "",
}
Since GeneXus 17 Upgrade 5.
GAM - One Time Password for mobile