Using Gmail with oAuth2 for SMTP with MBS Plugin
A client asked about using Gmail with oAuth 2 and our SendMail functions in MBS FileMaker Plugin. We have an existing example for Microsoft Office 365, which we can adapt for Google Mail. We change a couple of URLs, the scope and then it works fine. But let's go step by step.
As part of the oAuth, we later get a callback. Usually this is for contacting a web server, but we like to do it locally. We use our WebHook functions to do within the FileMaker Pro application and catch the answer from the JavaScript running in the browser.
Set Variable [ $$WebHooks ; Value: MBS("WebHook.Create") ]
Set Variable [ $r ; Value: MBS("WebHook.Listen"; $$WebHooks; 9999) ]
Set Variable [ $r ; Value: MBS("WebHook.SetScript"; $$WebHooks; Get(FileName); "WebHookReceived") ]
Set Variable [ $text ; Value: "<html><p>Request arrived.</p></html>" ]
Set Variable [ $text ; Value: "HTTP/1.1 200 OK¶Server: MyServer 1.0¶Connection: close¶Content-Type: text/html¶Content-Length: 36¶¶" & $text ]
Set Variable [ $text ; Value: MBS( "Text.ReplaceNewline"; $Text; 3 ) ]
Set Variable [ $r ; Value: MBS("WebHook.SetAutoAnswer"; $$Webhooks; $text; "UTF-8") ]
As you see we let the WebHook just answer any request with OK and a short html answer. That one is sent to the JavaScript in the WebViewer and it only cares for the 200 OK. The plugin triggers a script to report the incoming connection.
Next we like to load the sign-in page for the google service into the WebViewer. We pass our localhost URL with the port 9999 to taget the WebHook above. Then we request permissions for the mail scope. Faking the custom user agent often helps if the website rejects the WebViewer.
Set Variable [ $clientID ; Value: Trim(GMail oAuth SMTP::ClientID) ]
Set Variable [ $TenantID ; Value: Trim(GMail oAuth SMTP::TenantID) ]
Set Variable [ $redirectURI ; Value: "http://localhost:9999/" ]
Set Variable [ $redirectURI ; Value: MBS("Text.EncodeURLComponent"; $redirectURI; "UTF-8") ]
Set Variable [ $scope ; Value: "https://mail.google.com/" ]
Set Variable [ $scope ; Value: MBS("Text.EncodeURLComponent"; $scope; "UTF-8") ]
Set Variable [ $URL ; Value: "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&scope=" & $scope & "&redirect_uri=" & $redirectURI & "&client_id=" & $clientID & "&state=test&prompt=consent" ]
# let web viewer be Safari
Set Variable [ $r ; Value: MBS("WebView.SetCustomUserAgent"; "web"; "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15") ]
# Load the URL
Set Variable [ $r ; Value: MBS( "WebView.LoadURL"; "web"; $URL) ]
We have a callback script for the WebHook and then query the raw data from the request for later. Since we may get additional requests like favicon.ico, we just ignore these.
Set Variable [ $WebRequest ; Value: Get(ScriptParameter) ]
#
# we got the answer
Set Variable [ $URLComponents ; Value: MBS( "WebRequest.URLComponents"; $WebRequest ) ]
If [ Position ( JSONGetElement ( $URLComponents ; "RelativeURL" ); "favicon.ico"; 1; 1 ) > 0 ]
# ignore query for favicon
Exit Script [ Text Result: ]
End If
Set Field [ GMail oAuth SMTP::Answer ; $URLComponents ]
#
# and show full request for debugging
Set Variable [ $Text ; Value: MBS( "Text.ReplaceNewline"; MBS("WebRequest.GetRawData"; $WebRequest; "UTF-8"); 1) ]
Set Field [ GMail oAuth SMTP::Debug ; $Text ]
#
# free the webhook and request
Set Variable [ $r ; Value: MBS("WebRequest.Release"; $WebRequest) ]
Set Variable [ $r ; Value: MBS("WebHook.Release"; $$WebHooks) ]
Set Variable [ $$WebHooks ; Value: "" ]
#
# clear web viewer
Set Variable [ $r ; Value: MBS( "WebView.LoadURL"; "web"; "about:blank") ]
Next we need to extract the code from the answer and then make a request to the Google
Set Variable [ $URLComponents ; Value: GMail oAuth SMTP::Answer ]
Set Variable [ $Parameters ; Value: JSONGetElement ( $URLComponents ; "Parameters" ) ]
Set Variable [ $code ; Value: JSONGetElement ( $Parameters ; "code" ) ]
Set Variable [ $state ; Value: JSONGetElement ( $Parameters ; "state" ) ]
Set Variable [ $session_state ; Value: JSONGetElement ( $Parameters ; "session_state" ) ]
Once we have the code, we run a request to turn it into an access code with the google server.
Set Variable [ $clientID ; Value: Trim ( GMail oAuth SMTP::ClientID ) ]
Set Variable [ $clientSecret ; Value: Trim(GMail oAuth SMTP::ClientSecret) ]
Set Variable [ $URL ; Value: "https://oauth2.googleapis.com/token" ]
Set Variable [ $redirectURI ; Value: MBS("Text.EncodeURLComponent"; "http://localhost:9999/"; "UTF-8") ]
#
Set Variable [ $Data ; Value: "code=" & $code & "&client_id=" & $clientID & "&client_secret=" & $clientSecret & "&grant_type=authorization_code" & "&redirect_uri=" & $redirectURI ]
Set Variable [ $curl ; Value: MBS("CURL.New") ]
Set Variable [ $result ; Value: MBS("CURL.SetOptionURL"; $curl; $URL) ]
Set Variable [ $result ; Value: MBS("CURL.SetOptionPostFields"; $curl; $Data; "UTF-8") ]
Set Variable [ $result ; Value: MBS("CURL.SetOptionHTTPHeader"; $curl; "application/x-www-form-urlencoded") ]
Set Variable [ $result ; Value: MBS("CURL.Perform"; $curl) ]
# Pick Result
Set Variable [ $code ; Value: MBS( "CURL.GetResponseCode"; $curl ) ]
Set Variable [ $resultText ; Value: MBS("CURL.GetResultAsText"; $curl) ]
Set Variable [ $debugText ; Value: MBS("CURL.GetDebugAsText"; $curl) ]
Set Field [ GMail oAuth SMTP::CURL Debug ; $DebugText ]
Set Field [ GMail oAuth SMTP::CURL Result ; $resultText ]
If [ $result = "OK" and $code = 200 ]
Perform Script [ “Extract Access Token” ; Specified: From list ; Parameter: ]
Else
Show Custom Dialog [ "SMTP" ; "Failed to query token." ]
End If
Set Variable [ $result ; Value: MBS("CURL.Cleanup"; $curl) ]
We got an answer and now need to parse it from the JSON answer and store it in a field.
Set Variable [ $Result ; Value: GMail oAuth SMTP::CURL Result ]
# get access token
Set Variable [ $token_type ; Value: JSONGetElement ( $Result ; "token_type" ) ]
Set Variable [ $scope ; Value: JSONGetElement ( $Result ; "scope" ) ]
Set Variable [ $expires_in ; Value: JSONGetElement ( $Result ; "expires_in" ) ]
Set Variable [ $ext_expires_in ; Value: JSONGetElement ( $Result ; "ext_expires_in" ) ]
Set Variable [ $access_token ; Value: JSONGetElement ( $Result ; "access_token" ) ]
Set Variable [ $refresh_token ; Value: JSONGetElement ( $Result ; "refresh_token" ) ]
#
If [ Length ( $access_token ) > 0 ]
Set Field [ GMail oAuth SMTP::access_token ; $access_token ]
If [ Length ( $refresh_token ) > 0 ]
# we only get one, if offline_access scope is set
Set Field [ GMail oAuth SMTP::refresh_token ; $refresh_token ]
End If
Show Custom Dialog [ "Got token!" ]
End If
When you send an email, you pass the access token with the CURL.SetOptionXOAuth2Bearer option instead of passing a user name and password. Make sure you request TLS 1.2 or 1.3 like this:
# use google server
Set Variable [ $r ; Value: MBS("SendMail.SetSMTPServer"; $EmailID; "smtp.gmail.com") ]
# later set options for curl:
Set Variable [ $r ; Value: MBS("CURL.SetOptionPort"; $curl; 587) ]
Set Variable [ $r ; Value: MBS("CURL.SetOptionUseSSL"; $curl; 3) ]
Set Variable [ $r ; Value: MBS("CURL.SetOptionSSLVersion"; $curl; 6) ]
Set Variable [ $r ; Value: MBS("CURL.SetOptionXOAuth2Bearer"; $curl; GMail oAuth SMTP::access_token) ]
Finally you need to do a refresh of the token regularly. The access token is only valid a limited time, so you may refresh the token every day or if it is older than an hour.
The example file will be included in next plugin version with the example for Microsoft Office 365. Or email us if you need a copy.