- Motivation
JavaScript authentication with Gapi is both impressive and frustrating. Frustrating because in most of the examples you come across, and indeed in Google’s own guides, gapi is the center of the universe. They are about how to integrate your app with gapi, as opposed to the other way round – how to add gapi to your app. Gapi is super impressive in the way it takes care of the dirty details of oauth, but with frameworks like Vuejs and React the starter guides don’t fit that well.
Most Google authentication examples in Vue use Firebase, which is a super simple way of building in authentication, but if you need authorization for Google APIS as well, Firebase is not a great choice as it doesn’t know how to take care of the refresh token cycle, even as a Google provider. In this example we’ll take a look at how to integrate gapi with Vue. I’ve stripped out most of the error handling noise as how you handle that will be unique to your app (I use Vuetify v-bottom-sheet to communicate app errors)
Getting started
I’m assuming if you’ve read this far, you already know how to set up a Google project, get credentials and an API key, enable the necessary APIs etc so I’m not going to bother repeating all that. Here’s the official overview if you need it. Neither am I going to go into the details of how to set up a vuecli app or add vuex to it. I assume you know all that and have an app ready to go. So my assumption is
- You have a cloud project setup and have the necessary credentials and API key
- You have a Vue project ready to go
- You are using Vuex
- I’m using Vuetify for material design on the component examples. If youre not, you’ll need to modify to whatever it is you are using instead
Adding gapi
You’ll be using NodeJs to develop your Vue app, and you’d ideally like to add the the googleapis module. However, if you do this (everybody always wastes a lot of time on this), you’ll get this error from the Vue webpack configuration (because of the node-pre-gyp
dependency) so instead you’re going to need to add it from a cdn into your index.html. There are fancy things you can do with async loading etc, but I’ll just keep it simple.
Files
You’ll have your own organization so you’ll need to adapt as required, but the import files we’ll touch here for me are
- src/js/storeinitial.js – where the initial state for the vuex store is set up
- src/js/auth.js – all code to do with auth
- secrets/config.js – stuff in .gitignore I don’t want to commit to github such as my Google cloud project configuration details
- src/main.js – the app
Scopes
For this app (https://scrviz.web.app) I’m going to need these scopes, which i’ll set up in in secrets/config.js
I’m going to use the Drive picker and the Apps Script API, so I’ll need these discovery docs – more about them later. Note that the scopes are in fact space delimited for gapi (as opposed to an array for firebase), which is why I join them here, so take note to avoid wasting a lot of time on that.
Vuex store
I like to put everything that’s required across components in the store, but with the details of store actions in a script file dedicated to that subject. So although gapi is all executed from auth.js, they are accessed via the store. So here’s what I have in storeinitial.js. You may not need all this, but you probably will.
Auth
You’ll notice that various imports are referred to in storeinitial. They are defined in auth.js. Let’s take a look at what they do.
Import the secrets
These are kept in a separate file to avoid committing them to github. It’s impossible to completely hide secrets client side, which is why it’s important to properly scope your apikey and api enablement in your cloud project, but you don’t want them on github either. Another way to do this is use .env variables. In any case, luckily gapi is able to work with these ‘semi-secret’ credentials.
Signin
The interface to signin is via a store dispatch action. It simply calls this gapi method
Signout
Similarily interface to signin is via a store dispatch action . It simply calls this gapi method
Picker
I’ll be using the Drive picker in this app, so this is just a pass through to the initial store of credentials required to use the picker
Initializing gapi
The first action after loading the required gapi modules is to initialize gapi. This is called when the gapi action is dispatched to the store. Note the onUser argument – this is a function that gapi should call when there’s a change of user.
In this case, it commits the new user to store as directed by the storeinitial action
Checking scopes
It’s possible that a user can be logged in, but not have adequate scopes – perhaps your app doesnt request all the scopes it needs on initial login. It’s a good idea to check on whether any api call is likely to succeed by ensuring you have all the scopes granted. What’s happening here is that the scopes required by the app are being checked against those actually granted. The client app can then request more if any are missing
Requesting extra scopes
If you find you need more scopes for a signed in user you can do this to request the additional ones you need
Kicking it all off
Now everything is set up, and we can go back to main.js, just after we’ve initialized the Vuex store and get gapi to do its thing. It’s as simple as this.
Gapi will attempt to login and reauthorize (if required) the current user. All the user profile data and so on will be available from the store via the getters we previously set up.
The components
Now we have gapi ticking over, we can integrate them with out components. Here’s my loginchip component – if the user is logged in, it’ll show their avatar if they have one, or a person icon if they don’t. It’ll also show a login icon (I have a custom <icons> component – you’d probaby use a regular <v-icon> component here instead).
which produces this when logged in
and this when not
Discovery docs
Back at gapi initialization stage, we included discovery docs in the configuration. That’s because gapi doesn’t actually know how to access any given API. The discovery docs are a generalized way for it to build a client you can use to access any given api. It’s really easy to call them. Note that you don’t need to worry about access tokens. gapi will get one for you and keep it refreshed in some behind the scenes magic.
Here’s an example of calling the script API from gapi. It really is this simple
Scope checking
Previously we set up a way to check the granted scopes contain all the requested scopes. At some point in my app I need to make that call to an api that needs given scopes. I can conditionally set up the button to behave different if I’m not logged in, logged but not with enough scopes, or completely ready to go.
Here it is in not logged in state
versus ready to go
The component
will take one of 3 states
computed properties
The input to these states are all maintained automatically by gapi in the vuex store so this is all we have to do in the component itself.
auth method
The picker
The picker is a bit special. Even though we’ve provided the Drive Discovery document to gapi, we still need to build the kind of picker we want. In my case, I want to show all Google Workspace documents that can host an Apps Script project.
Here’s the entire picker component, showing how to add views. Note also that we need to pull in the projectId, the apikey and a access token all of which are available via the vuex store. (You’ll notice that use customized vuex mapActions, mapState, mapGetters and mapMutations modules to expose the store contents)
This produces a dialog that looks like this
Summary
This has been a longer article than normal, as I wanted to tell the whole story to provide a reusable starting point for gapi and vue together that’s a little more than the usual starter gapi examples.