Below is a possible solution to the problem posed in HowTo:Using GAM in a distributed application, where back-end services lack integrated security (Integrated Security Level property = none).
In the logic layer, the service "ReturnCustomers", is a Rest service (servicio Rest, Expose property as a Web Service= TRUE, Rest Protocol = TRUE) whose code is basically as follows:
// Validate the session token received as a parameter
&ValidAccessToken = GAMRepository.ValidAccessToken(&GAMSessionToken,&GAMSession,&Errors)
if &ValidAccessToken //If the session token received as a parameter is valid
//Continue with the execution flow of the program, load &CustomersSDT
//In the error output parameter (&GAMSessionSDT), indicate that there were no errors
else //If the session token received as a parameter is not valid
//Process the error, return it in the output parameter
endif
Rules:
parm(in:&GAMSessionToken,out:&CustomersSDT,out:&GAMSessionSDT);
Note:
- The service receives as a parameter a &GAMSessionToken variable of GAMSessionToken type that is validated using the ValidAccessToken method of the External Object GAMRepository. If it is valid, the program flow continues as usual, and client data is obtained. Otherwise, the error is recorded in the output variable &GAMSessionSDT.
- &GAMSessionSDT is an SDT that in this example allows recording the GAM error code.
- The service mentioned in the example returns the clients obtained from the database in the &CustomersSDT variable.
On the front end, a web panel that invokes this service must do it taking into account that it is a Rest service, and build the request using HttpClient data type.
Since the service receives an input parameter that is the &GAMSessionToken, this value must be sent in the request body.
&Token = &websession.Get("USERSession") // &websession is of WebSession type; I obtain the web session
if not &Token.IsEmpty() // If the session has not expired
//I build the body of the service request with the session token
&GAMSessionTokenSDT.GAMSessionToken = &Token
&body = &GAMSessionTokenSDT.ToJson()
//I invoke a procedure that makes the call to the Rest service
ProxyREST.Call(&server,&port,&baseURL,"ReturnCustomers","POST",&body,&HTTPstatus,&Result)
//I process the service request
if &HTTPStatus= 200
&ResultSDT.FromJson(&Result)
&GAMSessionSDT = &ResultSDT.GAMSessionSDT
if &GAMSessionSDT.GAMSessionOK
&customersSDT = &ResultSDT.CustomersSDT
// I process &customer SDT containing the list of clients
else
//The token is not valid; the login must be called
Login.Call()
endif
else //An error occurred when calling the service
endif
else
Login.Call()
endif
Note:
- Regardless of whether the token is valid, if the service is correctly invoked, the HttpStatus Code is 200. Its answer, which contains the error code indicating if the session is valid or not, must be processed.
- The SDTs defined for this example are as follows:
The ProxyREST process makes the call to the Rest service.
&httpclient.Host= &server
&httpclient.Port = &port
&httpclient.BaseUrl = &urlbase
&httpclient.AddString(&addstring)
&httpclient.AddHeader("Content-Type","application/json")
&httpclient.Execute(&method,&getstring)
&httpstatus = &httpclient.StatusCode
&result = &httpclient.ToString()
parm(&server,&port,&urlbase,&getstring,&method,&addstring,out:&httpstatus,out:&result)
Note:
- The corresponding Content-Type must be added to the request header.
In the logic layer, there must be a service (that we will call "LoginService") capable of validating the username and password received, and generating a valid session token that is returned to the caller.
The following code is an example of the service:
&LoginOK = GAMRepository.Login(&username,&userpassword,&AdditionalParameters,&Errors) // &Errors is of GAMError collection type
if not &LoginOK
// In the error output parameter (&GAMSessionSDT) indicate that there was an error
&GAMSessionSDT.GAMSessionToken = ''
&Error = &Errors.Item(1)
&GAMSessionSDT.GAMSessionErrorCode = &Error.Code
&GAMSessionSDT.GAMSessionError = &Error.Message
&GAMSessionSDT.GAMSessionOK = false
else
&GAMSession= GAMSession.Get(&Errors)
if &Errors.Count <> 0
&Error = &Errors.Item(1)
&GAMSessionSDT.GAMSessionError = &Error.Message
&GAMSessionSDT.GAMSessionErrorCode = &Error.Code
&GAMSessionSDT.GAMSessionOK = false
else //There were no errors when obtaining the GAM Session; return the Token assigned to the session in the output parameter
&GAMSessionSDT.GAMSessionToken = &GAMSession.Token
&GAMSessionSDT.GAMSessionOK = true
endif
endif
Rules:
parm(in:&username,in:&userpassword,out:&GAMSessionSDT);
In the front end, the login web panel must call this service ("LoginService").
// Build the service request body to include the input parameters (&username, &userpassword)
&UserCredentialsSDT.username = &UserName.Trim()
&UserCredentialsSDT.userpassword = &UserPassword.Trim()
&body = &UserCredentialsSDT.ToJson()
// I invoke a procedure that makes the call to the Rest service
ProxyREST.Call(&server,&port,&baseURL,"LoginService","POST",&body,&HTTPstatus,&Result)
if &HttpStatus = 200
//I process the service response
&ResultLogin.FromJson(&Result) //Retrieve the service response in an SDT
&GAMSessionSDT = &ResultLogin.GAMSessionSDT
if &GAMSessionSDT.GAMSessionOK
&Token = &GAMSessionSDT.GAMSessionToken //Retrieve the Token provided by the service.
&websession.Set("USERSession",&Token) //Save the session token in the web session.
// Redirect to the caller
else
//Process the error.
endif
else
// Error in the invocation to the service; process the error.
endif
Note:
- In the example, the service output variable, &GAMSessionSDT, contains the error code that must be processed to provide feedback to the user. For example, its content can be as follows:
{"GAMSessionSDT":{"GAMSessionOK":false,"GAMSessionError":"The user or password is incorrect.","GAMSessionErrorCode":"11","GAMSessionToken":""}}
- The image below shows the SDTs defined to develop the example:
- Using HTTPs is recommended to ensure communication through the channel.
- The session expiry, in this case, is ruled by the WebSessionTimeout property of the Security Policy applied to the user. Read Security Session Management in Applications using GAM. Remember that since they are web services they don't have web sessions; therefore, it is independent of the server web session.
- Remember that the application must have a logout feature, and it must run the deletion of the session on the server side.
The logout feature must be implemented on the server side.
To this end, a Rest service must be invoked to run the logout method of the GAMRepository object. In this way, the session token is invalidated in the GeneXus Access Manager (GAM).
It must contain the following code:
&er = GAMRepository.Logout(&Errors) // &Errors is a GAMError collection. The method returns a boolean value.
//Process the errors.
The Rest service (suppose that it is called "LogoutProcedure") is invoked with the headers ('GeneXus-Agent','SmartDevice Application') and ('Authorization','OAuth ' + Access_Token), as shown in the example below:
&httpclient.Host= &server
&httpclient.Port = &port
&httpclient.BaseUrl = &urlbase
&httpclient.AddString(&addstring)
&tokenvalue= 'OAuth ' + &Token
&httpclient.AddHeader('Authorization',&tokenvalue)
&httpclient.AddHeader('GeneXus-Agent','SmartDevice Application')
&httpclient.AddHeader('Content-type','application/json')
&httpclient.Execute("POST","LogoutProcedure")
&httpstatus = &httpclient.StatusCode
&result = &httpclient.ToString()
Note: The logout method of the GAMRepository object invalidates the session token when its headers receive information corresponding to this token, as in this example.
The implementation of this method depends on whether the headers are received. If they are not received, it is assumed that the execution platform is a web platform, and an attempt is made to delete the web session (it is not the case in this example).
Tcptrace and WizTools RestClient can be used for troubleshooting during development.