Search code examples
firebasefirebase-authenticationgoogle-api-js-clientgoogle-identity

How to retrieve email address from id_token Google Javascript API client (GAPI)


I have an SPA with Firebase backend and have integrated Google Calendar access.

To be able to authorise a user to use his/her Google Calendar I am using the gapi.auth2.authorize(params, callback) method. (this as opposed to the regular gapi.auth2.init and signIn flow because my users can link multiple Calendar accounts)

Docs: gapi.auth2.authorize

The problem I am experiencing:

Sometimes the id_token that is returned from authorize includes an email address, and sometimes it doesn't.

The id_token which is returned is a long string that can be read on the front end with a JavaScript function like so:

    function parseJwt (token) {
      let base64Url = token.split('.')[1]
      let base64 = base64Url.replace('-', '+').replace('_', '/')
      return JSON.parse(window.atob(base64))
    }

When I parse the id_token, I am expecting an object including an email address. However sometimes it doesn't include the email property at all.... How can I retrieve the user's google calendar email address from this id_token in with JavaScript, so I can save it to the user's firestore DB?

Example of an expected result when parsing the id_token:

expected result

Example of an un-expected result (no email):

unexpected result

Possible cause:

I think that it might be related to the accounts not returning an email being a Google G-Suite account? And the ones that do return the email is a regular gmail account? But I don't know the solution.

PS:
My flow for re-authorisation for return users is to just use the same gapi.auth2.authorize but with {prompt: 'none', login_hint: 'emailaddress'} and fill in the user's saved email address. This works fine.


Solution

  • In case you want to authorise the JavaScript client with gapi.auth2.authorize but also require the email address the user authorised for, be sure to include email in the scope of the gapi.auth2.authorize(params, callback) parameters!!

    A correct example of using JavaScript gapi for authorisation of Google calendar:

    Step 1.
    Include in main HTML head:

    <script type=text/javascript src="https://apis.google.com/js/api.js" async defer=defer></script>
    

    Step 2.
    (once) Load the client:
    window.gapi.load('client', callbackFunction)
    Important: Only load the client!

    Step 3.
    (once) Initialise the client for usage of Calendar API.
    Important: Only include the discovery docs!

    let calDocs = {
        discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest']
    }
    window.gapi.client.init(calDocs)
        .then(_ => {
          console.log('Calendar client initialised')
        })
      })
    },
    

    Step 4.
    (once) Authorise the gapi client for API calls with
    gapi.auth2.authorize(params, callbackFunction)
    Important: Scope is a string with spaces! Include email in the scope. Do NOT include the discovery docs here!

    params = {
        client_id: clientId,
        scope: 'https://www.googleapis.com/auth/calendar email',
        response_type: 'permission id_token'
    }
    

    You can repeat the gapi.auth2.authorize before any API call with extra params: {prompt: 'none', login_hint: 'emailaddress'} to refresh the user's access token. This will not show any prompt to the user if he already authorised once for your domain.