HowTo: Signout from a SSO applications not using GAM

Official Content

This article is related to HowTo: Implement SSO for applications that do not use GAM; in this case we are focusing on the logout step and how to do it on the Identity Provider side.

In short you need to execute a POST to the /oauth/gam/signout GAM Identity Provider service and then wait to be called locally and clean up your local session if needed.

Notify the Identity Provider

First of all, make sure you already implemented the first part of this tutorial.

The client application is already signed in using the GAM Identity Provider.

To execute a logout you need to call a GET to the service /oauth/gam/signout on the GAM IP and set several parameters directly on the URL; notice you need to handle some session data (token and state), parameters ( client_id, client_secret, redirect_uri and repository) and some fixes values.

  • oauth=signout
  • client_id = your_client_id (client ID from the calling application)
  • client_secret = your_server_id (client secret from the calling application)
  • server_ip =1
  • redirect_uri= The encoded redirect URL to be called afterwards.
  • token = session_token
  • first_call=1
  • state =session_state(*)
  • repository = server_repository_id (ask this parameter to the team in charge of the Identity Provider application)

    (*) session_state: is a random string (up to 40 characters) generated every time the logout method is executed.

You need to know the following parameters

  • your_client_id

A sample code looks as follows:

&RedirectURL = !"http://servername/applicationname/sampleobjectname.aspx" // Callback URL, any valid object
&ObjectName = !"oauth/gam/signout"
&state = &WebSession.Get(IdentityProviderParameters.State)
&Token = &WebSession.Get(IdentityProviderParameters.RemoteToken)

&EncodedURL = EncodeUrl.Udp(&RedirectURL) // (1)

&params = format(!"oauth=signout&client_id=%1&server_ip=1&redirect_uri=%2&client_secret=%3&token=%4&first_call=1&state=%5&repository=%6", 
    &ClientId.Trim(), // %1
    &EncodedURL.Trim(), // %2 
    &ClientSecret.Trim(), // %3
    &Token.Trim(), // %4
    &state.Trim(), // %5
    &Repository.Trim()) // %6
    
&url = format(!"http%1://%2:%3/%4/%5?%6",
    iif(&GAMIPSecure,!"s", !""),
    &GAMIPHost.Trim(),
    &GAMIPPort.Trim(),
    &GAMIPBaseURL.Trim(),
    &ObjectName.Trim(), 
    &params.Trim()
    )
link(&url)

The execution of the URL (&url) checks in the Identity Provider GAM if there is a valid session. If so, the session is finished. Afterwards, the URL specified in the redirect_uri parameter is executed by a GET HTTP.

Note: Since GeneXus 16 upgrade 9, the client_secret isn't included in the URL parameters.

Identity Provider Callback

When a logout operation is being processed on the Identity Provider, depending on the registered applications and valid sessions, a callback operation to each client application will be executed.

You need to create a Main Http Procedure to process the request (associated to the entry point "/oauth/gam/signout") and execute your logout logic; then call the Identity Provider again.

A possible code is the following:

// rules
// Response parsing    
&aParameters = &Response.SplitRegEx(!"&")
Do while &i <= &aParameters.Count
    &Text = &aParameters.item(&i)
    &VarReg = &Text.SplitRegEx(!"=")
    If &VarReg.Count > 1
        Do Case
            Case &VarReg.Item(1).Trim() = !"repository"
                //Repository GUID is needed when multi-tenant
                &GUIDRepServer = &VarReg.Item(2).Trim()
            Case &VarReg.item(1).Trim() = !"client_id" // Client ID must match the APP GUID
                &ClientId = &VarReg.item(2).Trim()
            Case &VarReg.item(1).Trim() = !"server_ip" //1 for the IP (Server)
                &isServerIP = &VarReg.item(2).ToNumeric()
            Case &VarReg.item(1).Trim() = !"first_call" //1 if it is the first call, 0 otherwise
                &FirstCall = &VarReg.item(2).ToNumeric()
            Case &VarReg.item(1).Trim() = !"redirect_uri"
                &OriginalLogoutURL = &VarReg.item(2).Trim()
            Case &VarReg.item(1).Trim() = !"client_secret"
                &ApplicationCliSecret = &VarReg.item(2).Trim()
            Case &VarReg.item(1).Trim() = !"token"
                &TokenToFinish = &VarReg.item(2).Trim()
            Case &VarReg.item(1).Trim() = !"state"
                &GAMTokenState = &VarReg.item(2).Trim()
            Case &VarReg.item(1).Trim() = !"key"
                &Parm_Key             = &VarReg.item(2).Trim()
                &isOauthEncrypted    = True
        EndCase
    Endif
    &i += 1
enddo

// Validate if the parameters are encrypted
if not &Parm_Key.IsEmpty()
    &AppCliEncKey     = GetRemoteKey() // your code here
    &ResponseEnc     = Decrypt64(&Parm_Key, &AppCliEncKey)
    &aParametersEnc = &ResponseEnc.SplitRegEx(!"&")
    If &aParametersEnc.Count = 3
        &i = 1
        Do while &i <= &aParametersEnc.Count
            &Text = &aParametersEnc.item(&i)  // simple parsing
            &VarReg   = &Text.SplitRegEx(!"=")
            If &VarReg.Count > 1
                Do Case
                    Case &VarReg.item(1).Trim() = "redirect_uri"
                        &OriginalLogoutURL = &VarReg.item(2).Trim()
                    Case &VarReg.item(1).Trim() = "client_secret"
                        &ApplicationCliSecret = &VarReg.item(2).Trim()
                    Case &VarReg.item(1).Trim() = "token"
                        &TokenToFinish = &VarReg.item(2).Trim()
                    Case &VarReg.item(1).Trim() = "first_call"
                        &FirstCall = &VarReg.item(2).ToNumeric()
                    Case &VarReg.item(1).Trim() = "state"
                        &GAMTokenState = &VarReg.item(2).Trim()
                EndCase
            Endif
            &i = &i + 1
        EndDo
    endif
Endif                        
                        
If not &GAMTokenState.IsEmpty() and &isServerIP = 0    
    If not &TokenToFinish.isEmpty()
        Do 'LocalLogout'
    Endif
Endif

&isServerIP = 1
&FirstCall = 0

// Server redirection
&GAMIPClientId = GetDynParameter.Udp(IdentityProviderParameters.ClientId, !"")
&GAMIPClientSecret = GetDynParameter.Udp(IdentityProviderParameters.ClientSecret, !"")
&GAMIPBaseURL = GetDynParameter.Udp(IdentityProviderParameters.App, !"")
&GAMIPBaseURL += !"oauth/gam/signout"

&GAMIPEncryptionKey = GetDynParameter.Udp(IdentityProviderParameters.EncryptionKey, IdentityProviderParameters.EncryptionKeyDefault)
&GAMIPEncryptParameter = iif( GetDynParameter.Udp(IdentityProviderParameters.EncryptParameter, !"") = !"1" , true, false)

&OriginalLogoutURL = EncodeUrl.Udp(&OriginalLogoutURL)
    
&GAMRemoteParam = Format(!"redirect_uri=%1&client_secret=%2&token=%3&first_call=%4&state=%5",
    &OriginalLogoutURL.Trim(), 
    &GAMIPClientSecret.Trim(),
    &TokenToFinish.Trim(),
    &FirstCall,
    &GAMTokenState 
)

If &GAMIPEncryptParameter
    GXTechnicalEncrypt(&GAMRemoteParam, &GAMIPEncryptionKey)
    EncodeUrl(&GAMRemoteParam, &GAMRemoteParam)
    &GAMRemoteParam = format(!"key=%1", &GAMRemoteParam.Trim())
Endif
    
If &RedirectRepositoryGUID.IsEmpty()
    &GAMRemoteServer = Format(!"%1?oauth=signout&client_id=%2&server_ip=%3&%4", &GAMIPBaseURL.Trim(), &GAMIPClientId.Trim(), &isServerIP, &GAMRemoteParam )
Else
    &GAMRemoteServer = Format(!"%1?oauth=signout&repository=%2&client_id=%3&server_ip=%4&%5", 
          &GAMIPBaseURL.Trim(), &RedirectRepositoryGUID.Trim(), &GAMIPClientId.Trim(), &isServerIP, &GAMRemoteParam )
Endif

Link(&GAMRemoteServer)

Sub 'LocalLogout'
    // Logout local session
    &WebSession.Destroy()
EndSub

Some comments:

  • The response is parsed to get parameters
  • The local session is destroyed (your code here)
  • The Identity Provider (/oauth/gam/signout) is called using the parameters provided and your credentials.

Notes for the environment configuration

C#

If the client application is generated with C#, you need to add the following lines to the web.config file under the rules section:

<!--GXIGNORE_START-->
<rule name="GXGamCallback" stopProcessing="true">
  <match url="^oauth/gam/signout$" />
  <action type="Rewrite" url="alogoutresponse.aspx" />
</rule>
<!--GXIGNORE_END-->

where "logoutresponse" references the GeneXus procedure in charge of the processing.

Java

  • If the client application is generated with Java, you need to add the following lines to the web.xml file in the WEB-INF directory of the client web app:
<servlet>
  <servlet-name>GAMOAuthCallback</servlet-name>
  <servlet-class>alogoutresponse</servlet-class>                                                                         
</servlet>
<servlet-mapping>
  <servlet-name>GAMOAuthCallback</servlet-name>
  <url-pattern>/oauth/gam/signout</url-pattern>
</servlet-mapping>

Availability

This behavior is available since GeneXus 15.

See Also

Single Sign On in applications using GAM
GAM Remote Authentication Type

Notes

(1) - The code associated to the EncodeUrl procedure is the following:

&URLEncoded = urlencode(&UrlToEncode)

Make sure to change the Standard Functions Object property to: allow non-standard functions.