Best Practice for Authentication Via "Sign in With Google" and Follow-up Authorization for a Node-Powered App that Accesses Google Sheets

320 views Asked by At

So I'm building a web app that's going to read and write data to a Google Sheet (I know - not ideal). This is for an educational organization with Google accounts for employees. The backend is node.js, express etc. I've implemented OpenID Connect via the newer "Sign in with Google" flow. The end user signs in in the browser, and Google sends the ID token via redirect to my server. Fine. I use the Google token-verification library for node to verify and decode the ID token. I look the user up in my db (for now just the Sheet). Authentication is essentially done.

I've created a service worker for the Google Cloud Project that's associated with this. My node server has the credential/key.json file for that service worker; scopes include Google APIs for Sheets etc. The service worker is fully authorized to read and write to the sheet, and of course, the sheet is "shared" with the service worker. Now come the questions:

  1. At this point, since I know the identity of any end user from the Google ID token, is it simply enough to create my own authorization flows based on who the user is? I control the service worker and can thus write logic in my server to allow certain users to perform certain actions on the Google sheet. These authorizations can be very finely grained. Simple. Is there any problem here, assuming, of course that my server itself remains secure. With all of the discussion of OAuth2.0 and access tokens, refresh tokens etc. should I be thinking differently?

  2. (Bigger question) How to handle subsequent requests from the browser? Sure, I can start a session when I authenticate the user and handle it that way. Or I can use my own JWT sent back to the client/browser and set in a secure http-only cookie. But can't I just send back the JWT that I already have ... the actual Google ID token? This has been asked on a few security forums in the past and no one can seem to give a solid answer. Sure the OpenID Connect docs say "start a session." And we know the session vs (irrevocable) JWT debate continues. But nowhere on the JWT side can I seem to find anyone really discouraging me from using the ID token instead. There are even a few tutorials that do it this way. And yet it still "feels" wrong to me. Even given proper SSL/TLS it still feels dangerous: a stolen ID token could be used in someone else's web app, where a poorly-designed backend isn't checking the token's aud claim for example, or the expiration time - essentially allowing a nefarious user to utilize that web app as someone else. Strange to me that I can't find anyone saying "yeah, don't do this!"

1

There are 1 answers

0
Michal Trojanowski On

Ad.1. It seems to be a good solution. Since it's the service worker that is the owner of the sheet, then it's up to your backend to perform authorization based on the identity of the user. You get the identity from Google (via the ID token), then you make your own decision whether to allow that user to read or write to your sheet.

Ad.2.

You should start your own session for the user.

  1. Why session and not a JWT (access token)?

It seems that you have a website, not an API. So your backend is responsible for authenticating the user, serving views to the browser, handling requests, etc. If this is truly the case, then it's simpler to use plain-old HTTP sessions with cookies (which you can make secure through features like sameSite and https). Also, cookies are automatically handled by the browser, and you have full control over the session in the backend (you can easily end the session when needed). When you send JWTs to the browser, then you need Javascript code that will handle the tokens and attach them to subsequent requests. Handling tokens in the browser is currently considered less safe than using cookies.

  1. "But can't I just send back the JWT that I already have ... the actual Google ID token"

Yeah, don't do this! Here, now you have it ;) Here's why it's a bad idea. The ID token that you get from Google is an indication that a user has authenticated at Google and Google tells you who the user is. The ID token is not equivalent to a user's session. The ID token from Google will have a relatively short life span, and you have no way of refreshing that token other than asking the user to sign in again (you can send a "silent" authorization request, but this has some limitations - browsers may drop support for that, once third-party cookies are blocked everywhere).

Another problem - the user logs in to your system using the ID token from Google. The token is valid for (let's say) 15 minutes. After 5 minutes the user logs out from Google but stays logged in to your site because you assume that the session is valid as the ID token hasn't expired.

The ID token is not meant to be a replacement for session handling. There is a draft spec that adds that functionality: https://openid.net/specs/openid-connect-session-1_0.html but a) it's still a draft and b) as mentioned before, it won't work once support for third-party cookies is dropped in browsers.

To summarize - create your own session based on the ID token. Use cookies-based sessions wherever you can.