Example 3: Implementing Google+ authentication with a URL-based web component

This example shows how to authenticate the user with a google+ account on a mobile platform, using the OAuth technology.

The form file: wc_oauth.per

LAYOUT(text="Proceed to the authorization")
GRID
{
[f1                 ]
[                   ]
[                   ]
}
END
END
ATTRIBUTES
WEBCOMPONENT f1 = FORMONLY.wc_oauth, STRETCH=BOTH;
END

The Google+ API utility file: wc_oauth.4gl

# Google+ Authorization API:
# See https://developers.google.com/accounts/docs/OAuth2InstalledApp

IMPORT com
IMPORT util

# The persistant datastore
PRIVATE DEFINE datastore RECORD
      client_id           STRING,
      client_secret       STRING,
      authorization_code  STRING,
      expiration_date     DATETIME YEAR TO SECOND,
      auth_data RECORD
         access_token    STRING,
         token_type      STRING,
         expires_in      INTEGER,
         id_token        STRING,
         refresh_token   STRING
      END RECORD,
      user_info RECORD
         id              STRING,
         name            STRING,
         link            STRING,    # google plus profile URL
         picture         STRING,    # face URL
         email           STRING
      END RECORD
   END RECORD

#+ This function checks if the google account is authorized and manages to get authorization
#+ @return boolean
FUNCTION googleplus_isAuthorized()
   DEFINE httpReq         com.HttpRequest
   DEFINE httpPostData    STRING
   DEFINE httpResp        com.HttpResponse
   DEFINE httpRespData    STRING
   DEFINE authUrl         STRING

   LET datastore.client_id = "****999.apps.googleusercontent.com"
   LET datastore.client_secret = "rlg*******-HUB"

   # Check token expiration
   IF datastore.expiration_date > CURRENT YEAR TO SECOND THEN
      RETURN TRUE
   END IF
   # The authorization token expired
   # If we already have an authorization, we need to refresh our token
   # See https://developers.google.com/accounts/docs/OAuth2InstalledApp?hl=fr#refresh
   IF datastore.auth_data.refresh_token.getLength() > 2 THEN
      # Refresh the token
      LET httpReq = com.HttpRequest.Create("https://accounts.google.com/o/oauth2/token")
      CALL httpReq.setMethod("POST")
      LET httpPostData =
         SFMT(
            "client_id=%1&client_secret=%2&refresh_token=%3&grant_type=refresh_token",
            datastore.client_id,
            datastore.client_secret,
            datastore.auth_data.refresh_token
         )
   ELSE
      # Get an authorization code
      # See https://developers.google.com/accounts/docs/OAuth2InstalledApp?hl=fr#formingtheurl
      LET authUrl =
         SFMT( "https://accounts.google.com/o/oauth2/auth?"
             ||"response_type=code"
             ||"&client_id=%1"
             ||"&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
             ||"&scope=https://www.googleapis.com/auth/userinfo.email"
                ||"%%20https://www.googleapis.com/auth/userinfo.profile"
                ||"%%20https://www.googleapis.com/auth/plus.login",
            datastore.client_id
         )
      LET datastore.authorization_code = googleplus_getAuthorization(authUrl)
      IF datastore.authorization_code IS NULL THEN
         # User did not authorize the accesss to the data
         RETURN FALSE
      END IF
      # Ask for the first token
      # See https://developers.google.com/accounts/docs/OAuth2InstalledApp?hl=fr#handlingtheresponse
      LET httpReq = com.HttpRequest.Create("https://accounts.google.com/o/oauth2/token")
      CALL httpReq.setMethod("POST")
      LET httpPostData =
         SFMT("code=%1&client_id=%2&client_secret=%3"
            ||"&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
            ||"&grant_type=authorization_code",
            datastore.authorization_code,
            datastore.client_id,
            datastore.client_secret
         )
   END IF
   TRY
      CALL httpReq.doFormEncodedRequest(httpPostData, FALSE)
      LET httpResp = httpReq.getResponse()
      IF httpResp.getStatusCode() <> 200 THEN
         RETURN FALSE
      END IF
      LET httpRespData = httpResp.getTextResponse()
      CALL util.JSON.parse(httpRespData, datastore.auth_data)
      LET datastore.expiration_date =
          CURRENT YEAR TO SECOND + ((datastore.auth_data.expires_in - 60) UNITS SECOND)
      RETURN (datastore.expiration_date > CURRENT YEAR TO SECOND)
   CATCH
      # Network error...
      RETURN FALSE
   END TRY
END FUNCTION

#+ This function manages the authentication and authorization UI for Google+
#+ @param authorizationUrl the built URL to display the authorization dialog on google website
#+ @return The authorization code
FUNCTION googleplus_getAuthorization(authorizationUrl)
   DEFINE authorizationUrl  STRING
   DEFINE authorizationCode STRING
   DEFINE wc_oauth          STRING
   DEFINE doc_title         STRING
   DEFINE flag              BOOLEAN

   DEFINE authorizationCodeBegin INTEGER
   DEFINE authorizationCodeEnd   INTEGER

   OPEN WINDOW w_oauth WITH FORM "wc_oauth"
   LET authorizationCode = NULL
   LET wc_oauth = authorizationUrl
   INPUT BY NAME wc_oauth ATTRIBUTES(WITHOUT DEFAULTS, ACCEPT=FALSE)
      ON CHANGE wc_oauth -- a new page is loaded in the webview
         CALL ui.Interface.frontCall("webcomponent","getTitle",["formonly.wc_oauth"],[doc_title])
         IF doc_title.getIndexOf("Success", 1) == 1 THEN
            LET authorizationCodeBegin = doc_title.getIndexOf("code=", 1)
            LET authorizationCodeEnd =  doc_title.getIndexOf("&", authorizationCodeBegin)
            IF authorizationCodeEnd = 0 THEN
               LET authorizationCodeEnd = doc_title.getLength() + 1
            END IF
            LET authorizationCode = doc_title.subString(authorizationCodeBegin+5, authorizationCodeEnd - 1)
            EXIT INPUT
         END IF
      ON ACTION cancel
         MENU "Confirmation"
              ATTRIBUTES(STYLE="dialog", COMMENT="Cancel the authorization process?")
            ON ACTION accept
               LET flag = TRUE
            ON ACTION cancel
               LET flag = FALSE
         END MENU
         IF flag THEN
            LET authorizationCode = NULL
            EXIT INPUT
         END IF
   END INPUT
   CLOSE WINDOW w_oauth
   RETURN authorizationCode
END FUNCTION

The program file: main.4gl

IMPORT FGL wc_oauth
MAIN
   MENU
       ON ACTION get_auth
          IF googleplus_isAuthorized() THEN
             MESSAGE "Google+ authorization acquired."
          ELSE
             ERROR "Unable to get Google+ authorization."
          END IF
       ON ACTION close
          EXIT MENU
   END MENU
END MAIN