bvstone

Google Sign-In Integration with your IBM i RPG Web Application

Posted:

Google Sign-In Integration with your IBM i RPG Web Application

Update 3/11/2022 - Google has updated this function to use slightly different methods and is requiring updates. 

More applications these days require some sort of authentication or method of "signing on" to the application so that we can track the user and customize the display for each individual.

This has been done in the past using hidden fields in forms (for example, each dynamic page will contain a hidden field with a user id) or even more useful, using Cookies to track each user.

Now there seems to be a push to go with "one sign on for everything."  The major players offering up this service are Google, Facebook and Linked In.  You've probably seen sites where you can create a user id, but they also allow you to "Sign on with Google" or one of the other players.  Not only does this make things easier for visitors of the sight by allowing them to sign up and/or sign on with a single click (not having to remember yet another user id and password), it increases security through the use of OAuth authentication and may also reduce the anonymity.

Recently for the FieldExit site we added this feature as well, allowing you to sign on with a Google ID.  This means you don't have to go through the entire signup process.  In as little as one click you are able to sign in to this sight and share your IBM i related applications, solutions and ideas.

Google has made this quite simple with some JavaScript functions that you can include in your web application.  To make things more secure you also should have a back end process that communicates with Google's servers to verify the information.  While this isn't necessary, Google strongly advises it be done.  We chose to go that route and decided to share how we implemented this Google Sign-In process here at FieldExit.com.

Getting Started

The first step, as outlined in the documentation provided by Google, is to create a Project and Client ID for your Sign-In application.  This is easier than it sounds and Google provides some decent documentation for this.

Once that is done, we need to include a JavaScript library in our application.  Because we use Server Side Includes (SSIs) to piece together our sites, adding this to each page on our site is a simple as adding a new line to our "JavaScript" SSI file:

<!-- Google Login OAuth -->
<script src="https://accounts.google.com/gsi/client" async defer></script>
<!-- end Google Login OAuth -->

This allows the code from Google to be loaded into your application.

Signing In

Once these things are in place we need to add our sign in button to our web page.  When a user clicks the Log In or Sign Up link in the top right hand corner of the site, they are taken out our login page.  At first it just allowed you to sign in if you already had an ID for FieldExit or allowed you to create a new account.

But by adding this simple HTML, we now have a button that allows you to sign in using your Google Account:

<div id="g_id_onload"
   data-client_id="your.google.application.id"
   data-callback="handleCredentialResponse">
</div>
<div class="g_id_signin" data-type="standard"></div>

We now need a Javascript function to handle the credential response (handleCredentialRespose):

function handleCredentialResponse(response) {
   // decodeJwtResponse() is a custom function defined by you
   // to decode the credential response.
   const responsePayload = decodeJwtResponse(response.credential);

   //console.log('Full Name: ' + responsePayload.name);
   //console.log('Given Name: ' + responsePayload.given_name);
   //console.log('Family Name: ' + responsePayload.family_name);
   //console.log("Image URL: " + responsePayload.picture);
   //console.log("Email: " + responsePayload.email);

  $.cookie("gbfGoogleID",JSON.stringify(responsePayload));
	var id_token = response.credential;
	$('#userid').removeClass('validateField');
	$('#password').removeClass('validateField');
	$('#googleid').val(id_token);
	$('#loginID').val(randomString(256));
	showMessage('Logging in with Google ID ' + responsePayload.email);

	$('#updateForm')[0].submit();
}

What is returned is a JWT token that can be parsed for individual items.  But in our case we want to pass the entire token to our RPG program as we will call a Google Verification endpoint to validate that the data isn't being spoofed, and actually came from Google.

Now, in order to understand what happens we need to first look at the HTML form that is used on our sign in page.  In the next source clip we've simplified it and removed some of the formatting so that the important pieces are more obvious:

<form id="updateForm" action="/forum/login" method="POST">
  <input name="id" id="loginID" type="hidden">
  <input name="googleid" id="googleid" type="hidden">
  User ID:<input class="validateField" id="userid" name="userid">
  <div class="error" id="useridError"></div>
  Password:<input class="validateField" id="password" name="password" type="password">
  <div class="error" id="passwordError"></div>
  <a class="button" id="loginButton" href="#">Log In</a>
</form>

First we see we have two hidden fields, id and googleid.  The id field is used internally for tracking the user, but the googleid field is used to store the Google ID (JWT token) that is retrieved when a user clicks on the Sign In with Google Button. 

We're using jQuery to do a few things.  First we remove the validateField class from the user id and password fields so that our validation routines won't be called when the form is submitted.  Next, we set the value of the hidden field googleid to the value of the ID token retrieved using the Google functions.  As stated in their documentation you can also retrieve all the other information you want, but for security reasons you should only use the ID and then validate that on the back end to make sure you're application is getting the real data from Google, and the information isn't being "spoofed" by another entity.

Finally, we show a message (which uses BlockUI, a jQuery library) to show the user we're signing on with their email address and we submit the form which calls an e-RPG program named LOGIN.

The LOGIN program validates the data, but most importantly uses a web service application (in this case our GETURI application) to make an HTTP request to Google to validate the ID that we retrieved from our web page.  The important part of the application (which uses the eRPG SDK) is as follows:

inUserID = #getData('userid');
inPW = #getData('password');
inID = #getData('id');
inGoogleID = #getData('googleid');


if (inGoogleID <> ' ' );
  exsr $GoogleID;
else;
  // do normal login
endif;

//-------------------------------------------------------------/
// Get Google ID (Clicked Google Signon Button)                /
//-------------------------------------------------------------/
begsr $GoogleID;

  #pushLib('GETURI');

  Clear GetUri_In;
  GetUri_Data = 'id_token=' + %trim(inGoogleID);
  GI_URI = 'https://www.googleapis.com/oauth2/v3/tokeninfo';
  GI_Port = 443;
  GI_Data = '*PARM';
  GI_ReqMeth = 'GET';
  GI_SSL = '*YES';
  GI_SprHead = '*YES';
  GI_CCSID = 1252;
  GI_Debug = '*YES';
  GI_DebugFile = '/tmp/googlesignondebug.txt';
  GI_OutType = '*RETURN';
  GI_CodPag = 1252;

  callp(e) #geturicall(GetUri_In:GetUri_Out:GetUri_Head:GetUri_Data:
                       GetUri_MsgCd:GetUri_Msg);

  if (%error);
    errMsg = 'GETURI Error: ' + %trim(GetUri_Msg);
    exsr $Error;
  endif;

  docNode = yajl_buf_load_tree(%addr(GetUri_Out:*DATA):          
                             %len(GetUri_Out):                 
                             errMsg);                          
                                                               
  if (docNode = *NULL);                                          
    errMsg = 'Error retrieving information from Google.';        
    exsr $Error;                                                 
  endif;                                                         

   errNode = YAJL_object_find(docNode:'error');                    
                                                                 
   if (errNode <> *NULL);                                          
     val = YAJL_object_find(errNode:'message');                    
     errMsg = yajl_get_string(val);                                
   endif;                                                          
                                                                 
   if (errMsg <> ' ');                                             
     yajl_tree_free(docNode);                                      
     exsr $Error;                                               
   endif;                                                       
                                                             
   val = YAJL_object_find(docNode:'aud');                       
   clientID = yajl_get_string(val);                             
                                                             
   if (clientID <>                                              
     'xxzxx' +        
     '.apps.googleusercontent.com');                          
     errMsg = 'Invalid client ID.(' + %trim(clientID) + ')';    
     yajl_tree_free(docNode);                             
     exsr $Error;                                         
   endif;                                                 
                                                       
   val = YAJL_object_find(docNode:'sub');                 
   gID = yajl_get_string(val);                            
                                                       
   if (gID = ' ');                                        
     errMsg = 'Google ID was blank.';                     
     yajl_tree_free(docNode);                             
     #popLib('GETURI');  
     exsr $Error;                                   
   endif;                                           
                                                 
   val = YAJL_object_find(docNode:'email');         
   gEMail = yajl_get_string(val);                   
                                                 
   if (gEMail = ' ');                               
     errMsg = 'Google EMail was blank.';            
     yajl_tree_free(docNode);                       
     exsr $Error;                                   
    endif;                             
                                    
   yajl_tree_free(docNode);           

endsr;

To validate the Google ID retrieved we call a specific Google API using GETURI (or any other HTTP client).  We then check for errors that may have occurred during the call.  If everything worked we should receive some JSON data that looks like the following:

{
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "xxxxxx-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "email": "billd1600@gmail.com",
 "at_hash": "X_B3Z3Fi4udZ2mf75RWo3w",
 "email_verified": "true",
 "aud": "xxxxxx-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953"
}

In our case we are using our JSON parser named JSONTOOL.  We load the data returned from GETURI into our JSON parser and then check to see if there is an error message in the JSON.  Because we've worked with a few of Googles APIs with our GreenTools for Google Apps (G4G) application we know that Google tends to return error messages in a standard format. 

If there is no error we retrieve the ID token from the JSON data and compare it against the ID token that was read into the application.  If that checks out (which means it's not being "spoofed"), we then parse out the email address and use that as the ID for the user that is signed in.

Once we have this email address following this back end server processing we can be sure that it's a valid account and allow the user to be signed on in which case they can add content on our site.

Signing Out

If a user wishes to sign out Google provides a function for that as well.  Our site provides a "Log Out" link at the top right that a user can click to sign out.  With this new sign in process we no longer need to make sure we disconnect from the Google account if that was the sign on method.  So, when the user clicks the Log Out link, the following JavaScript is run to simply clear some cookies used for sessions:

function logout() {

	if (confirm('Are you sure you want to log out?')) {
      $.cookie("gbfuser", null, { path: '/' });  
      $.cookie("gbfGoogleID", null, { path: '/' });  
      window.location.assign("/"); 
	  	return true;
	  } else {
		return false;
	 }
}

The user is prompted with a confirmation window and if the choose to sign out we call the signOut() application for the specific Google instance that is being used.  

So, as you can see implementing a 3rd party sign on to your application is not only fairly simple and straightforward, it can make your user's experience even better as it is one less user id and password that they will have to remember.  And if done correctly, should provide secure authentication and even user tracking to your applications!


Last edited 03/11/2022 at 11:12:35




Reply




© Copyright 1983-2024 BVSTools
GreenBoard(v3) Powered by the eRPG SDK, MAILTOOL Plus!, GreenTools for Google Apps, jQuery, jQuery UI, BlockUI, CKEditor and running on the IBM i (AKA AS/400, iSeries, System i).