Google OAuth2
Network-Security-OAuth2
Node-Express
Network-Cookie
03/03/2021
1. Google OAuth
Intro: OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service, such as Facebook, GitHub, and DigitalOcean. It works by delegating user authentication to the service that hosts the user account, and authorizing third-party applications to access the user account. OAuth 2 provides authorization flows for web and desktop applications, and mobile devices.
Simply put, if we want to use Google Account to login our application or connect gmail, we can use Google OAuth2 flow to verify the user, Google will handle the authentication flow and hand back the control to your application, togather with user's google account infomations.
for Nodejs, react-google-login
lib is enough to use. But if you wanna enhance your system's security, you can use state
param in Google OAuth2 api, however this param is not supported in react-google-login
, besides, the formal Javascript Google api lib gapi
doesn't support it as well! so you need to call googleapis with axios/ajax by yourself. follow the formal doc of type OAuth 2.0 Endpoints
2. JWT/Session
for node application's authentication, there're mainly 2 modern solutions:
- JWT
- Store session in server DB (like express-session)
the main difference for these 2 machanism is in this sof post, I concluded them as below:
- JWT store in client, so that low costing for server, boost the speed
- JWT may confront with XSS attact, Server-side sessions is safer
State Construct
unlike JWT, we store the user session values in Backend-DB (sessions tbl), in order to meet the requirement, validate state during the whole oauth flow on both Frontend&Backend, we use sessionID to generate state value, build the state the value as below
- in login page, if google-state not in localstorage, Frontend will call /loginWithGoogle with param {startOauth: true} , Backend will generate a “fake“ user (passport.js), thus initializing a new entry in sessions tbl, and return the sessionID as data in response.
- for gmail connect, the browser should contain the google-state in localstorage already, otherwise it should be treated as invalid user.
- Once Frontend receiving the sessionID, it generate state param using the sessionID with bcryptjs’s hash function, and store the sessionID with storageHelper into localstorage.
- Frontend hold the state value so it can directly validate the state value from Google OAuth response
- Backend will use bcrypt lib to compareSync the value between request sessionID and state_token passed from Frontend
Flow
implement Google OAuth2 authorization as End point (https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow?hl=fr), deprecate the react-google-login lib, call Google OAuth2 apis, handle the response on Frontend, then call corresponding APIs on Backend.
- Google Login
- enter login page, generate fake session to fetch sessionID
- if google-state not in localstorage, automatically call Backend API to generate new session, Backend will return sessionID
- if google-state aleady in localstorage, do nothing.
- user login
- if user choose normal login, the sessionID will be refreshed to a new one so that we can defense XSS attack, therefore after user logged in, Frontend will receive the updated sessionID and updated it in localstorage
- if user choose Google login,
- frontend call OAuth2 API with scope(email profile openid), response_type(token) and open a new mini window
- Google will handle the OAuth2 flow and let user to authorize it
- once confirmed, Google will redirect the link to the callback address which is specified to callback url
- in this page, Frontend will check state param, if passed, call /loginWithGoogle again, and pass user’s data and state_token as well to Backend
- Backend will use bcrypt to compareSync the state_token and sessionID first, once passed, follow the previous logics for this API.
- Gmail connect
- frontend call OAuth2 API with scope(email profile https://mail.google.com/), response_type(code) and open a new mini window
- Google will handle the OAuth2 flow and let user to authorize it
- once confirmed, Google will redirect the link to the callback address which is specified to callback url.
- in this page, Frontend will check state param, if passed, call backend connect gmail API , and pass code and state_token to Backend
- Backend will use bcrypt to compareSync the state_token and sessionID first, once passed, follow the previous logics for this API.
AWS ELB issue
Recently we use aws elb to add one layer between Frontend and Backend as proxy, in this way we can terminate SSL/TLS in front of the proxy, so that it will reduce network costs(encrypt) and boost response speed.
However the login flow doesn’t work in this network architecture, as we finally find, it’s caused by cookie issue.
The new flow will be as below:
- Frontend request HTTPS with nginx proxy
- nginx send HTTP request to Backend
cause Backend use express framework to handle HTTPS/HTTP request, there are some configs for response cookie set, one option is secure , if this value set to true, compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection.
and the express formal doc described:
If you have your node.js behind a proxy and are using secure: true, you need to set "trust proxy" in express
so use app.set('trust proxy', 1) make the flow works, Frontend can set csrf token into cookies, and the WebApp can pass session check.
Why 'trust proxy' works
as it mentioned, express use ‘trust proxy' to add X-Forwarded-Proto header, this header can make server trust the client and send response with cookies:
- Client sends an HTTPS request to the Proxy
- Proxy decrypts the HTTPS traffic and sets the "X-Forwarded-Proto: https"
- Proxy sends the HTTP request to the Server
- Server sees that the URL is "http://"" but also sees that "X-Forwarded-Proto" is "https" and trusts that the request is HTTPS
- Server sends back the requested web page or data