How to Setup Google OAuth2 Login with Parse Server in React
Tags: react, parse, javascript, baas
Published: 2020-11-23 20:50:23

I recently started to use Parse Server, and while checking the documentation I struggle to find out a direct way to configure my Parse application in a way to perform Google OAuth2. I decided to dig-in into the subject and consolidate a tutorial that explains step by step how to achieve that task.

According to Parse documentation, "Parse allows you to link your users with 3rd party authentication, enabling your users to sign up or log into your application using their existing identities. This is accomplished through linkWith method by providing authentication data for the service you wish to link to a user in the authData field. Once your user is associated with a service, the authData for the service will be stored with the user and is retrievable by logging in." To perform the user authentication, you can use the following method:

```javascript
const user = new Parse.User();
await user.linkWith('google', { authData: myAuthData });
```
The problem is, where shall we retrieve the Authentication Data? In this tutorial, we will find out.

If you just want to look at the code, a sample project is available on [GitHub](https://github.com/autodidaktum/google-oauth2-parse-react). A sample application is also hosted at [Back4App](https://userauth.b4a.app/).

## Configure Google Client Identifier

Log into your Google Developer account and go to Credentials. Click Create credentials and choose OAuth client ID

Pick the platform you will need. For this example, I am using Javascript (Web Application), but you should pick the one you will be using. Define your Authorized JavaScript origins, and Authorized redirect URIs. Afterwards, you should receive your Client ID and Secret.

## Create Parse Cloud Code
### Retrieve Client ID and Client Secret

Once you create your client ID and client secret, store those values as environment variables inside your Parse Server Application. You can find those under Server Settings -> Environment Variables

Create a package.json file, and add the googleapi dependencies:

```json
{ 
	"dependencies": {
		"googleapis": "^61.0.0"
	}
}
```
Now create the Parse Cloud Code to start the authentication with Google. This method will return an url where the user can login with a Google Account:

```javascript
Parse.Cloud.define("GoogleSignIn", async (request) => {
  const google = require("googleapis").google;
  // Google's OAuth2 client
  const OAuth2 = google.auth.OAuth2;

  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(
    process.env.client_id,
    process.env.client_secret,
    process.env.redirect_uris
  );
  // Obtain the google login link to which we'll send our users to give us access
  const loginLink = oauth2Client.generateAuthUrl({
    // Indicates that we need to be able to access data continously without the user constantly giving us consent
    access_type: "offline",
    // Using the access scopes from our config file
    scope: ["email", "openid", "profile"],
  });
  return loginLink;
});
```
### Retrieve User Info and Access Token
You need a second Parse Cloud Code in order to handle redirecting from the Google Authentication. This function will return some user information, the ID Token and Access Token, which later you can use in your Parse Application:
```javascript
Parse.Cloud.define("GoogleToken", async (request) => {
  const google = require("googleapis").google;

  // Google's OAuth2 client
  const OAuth2 = google.auth.OAuth2;

  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(
    process.env.client_id,
    process.env.client_secret,
    process.env.redirect_uris
  );

  if (request.error) {
    // The user did not give us permission.
    return request.error;
  } else {
    try {
      const { tokens } = await oauth2Client.getToken(request.params.code);
      oauth2Client.setCredentials(tokens);
      var oauth2 = google.oauth2({
        auth: oauth2Client,
        version: "v2",
      });
      const usr_info = await oauth2.userinfo.get();
      // Auth data for Parse
      const authData = {
        id: usr_info.data.id,
        email: usr_info.data.email,
        name: usr_info.data.name,
        id_token: tokens.id_token,
        access_token: tokens.access_token,
      };
      return authData;
    } catch (error) {
      return error;
    }
  }
});
```
Deploy your Cloud Code in your Parse Server.
## Create React Application
Let's create a React application, and install the following dependencies:
* react-bootstrap
* bootstrap
* parse
* react-router-dom
* redux
* react-redux
* redux-devtools-extension
* redux-thunk

### Initialize Parse Server
Use Parse.Initialize method to set up the authentication token, connecting your page with Parse Server. Go to the App.js file and add the following entry:
```javascript
import Parse from 'parse'

Parse.initialize(APPLICATION_ID, JAVASCRIPT_KEY);
Parse.serverURL = SERVER_URL;
```
The Application ID and JavaScript Key under App Settings -> Security & Keys.
### Add Redux Store Boilerplate
The application uses Redux in order to easily handle User Authenticate state across different pages. Create a folder redux, and inside a file named store.js. Then, add the necessary boilerplate for the store:
```javascript
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";

import rootReducer from "./reducers";

const initialState = {};
const middleware = [thunk];

const store = createStore(
  rootReducer,
  initialState,
  composeWithDevTools(applyMiddleware(...middleware))
);

export default store;
```
Add another folder inside redux called reducers, and create a file index.js. Then, add the necessary code to configure your root reducer
```javascript
import { combineReducers } from "redux";

export default combineReducers({});
```
Finally, go back to App.js and add the Store Provider. At the end, App.js should look like this
```javascript
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import * as Env from "./environment";

import { Provider } from "react-redux";

import Routes from "./routers/Routes";
import store from "./redux/store";

import Parse from "parse";

Parse.initialize(Env.APPLICATION_ID, Env.JAVASCRIPT_KEY);
Parse.serverURL = Env.SERVER_URL;

function App() {
  return (
    <Provider store={store}>
      <Routes />
    </Provider>
  );
}

export default App;
```
### Create Reducer and Action for Authentication
Now, under redux folder let's create another one called actions, and inside a file called auth.js and types.js. Inside types.js, let's add two entries
```javascript
export const REDIRECT_SUCCESS = "REDIRECT_SUCCESS";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const AUTH_ERROR = "AUTH_ERROR";
```
Under auth.js, let's define the actions to handle the Google Sign Up:
```javascript
import Parse from "parse";

import { REDIRECT_SUCCESS, LOGIN_SUCCESS, AUTH_ERROR } from "./types";

export const loginGoogle = () => async (dispatch) => {
  try {
    const res = await Parse.Cloud.run("GoogleSignIn");
    dispatch({
      type: REDIRECT_SUCCESS,
      payload: res,
    });
  } catch (error) {
    if (error) {
      dispatch({
        type: AUTH_ERROR,
        payload: error,
      });
    }
  }
};

export const getTokenGoogle = (params) => async (dispatch) => {
  try {
    // Get the Authentication Data
    const res = await Parse.Cloud.run('GoogleToken', params);
    const user = new Parse.User();
    const authData = {
      'id': res.id,
      'access_token': res.access_token,
    };

    await user.linkWith('google', { authData });
    user.set('username', res.name);
    user.set('email', res.email);
    await user.save();
    dispatch({
      type: LOGIN_SUCCESS,
    });
  } catch (error) {
    if (error) {
      dispatch({
        type: AUTH_ERROR,
        payload: error,
      });
    }
  }
};
```
Now, let's create our auth reducer. Here we define how the store will keep the User Information and Authentication Data:
```javascript
import { REDIRECT_SUCCESS, LOGIN_SUCCESS, AUTH_ERROR } from "../actions/types";

const initialState = {
  isAuthenticated: null,
  oauth: null,
  errors: [],
};

export default function (state = initialState, action) {
  const { type, payload } = action;

  switch (type) {
    case REDIRECT_SUCCESS:
      return {
        ...state,
        oauth: payload,
      };
    case LOGIN_SUCCESS:
      return {
        ...state,
        isAuthenticated: true,
      };
    case AUTH_ERROR:
      return {
        ...state,
        errors: payload,
      };
    default:
      return state;
  }
}
```
Don't forget to include your auth reducer in your combine reducer at index.js
```javascript
import { combineReducers } from "redux";
import auth from "./auth";

export default combineReducers({
  auth,
});
```
###Create Login Page
Create a new folder called pages, and inside a file named Home.js. Then, build a Form to allow the User to log in. We'll call the action loginWithGoogle, when the user presses the Sign In with Google button. We will redirect to the link provided by Google:
```javascript
import React, { Fragment, useState } from "react";
import { Form, Button } from "react-bootstrap";

import { connect } from "react-redux";
import PropTypes from "prop-types";

import { loginGoogle } from "../redux/actions/auth";

const Home = ({ loginGoogle, oauth }) => {
  const handleGoogleSignIn = () => {
    loginGoogle();
  };

  if (oauth) {
    window.location.href = oauth;
  }

  return (
    <Fragment>
      <div className='container'>
        <div className='display-1 text-primary mb-3 text-center'>
          Login Page
        </div>
        <div className='row align-items-center justify-content-center'>
          <div className='col-6'>
            <Form>
              <Form.Group>
                <Form.Control
                  type='email'
                  name='email'
                  placeholder='Email'
                  className='my-2'
                ></Form.Control>
                <Form.Control
                  type='password'
                  name='password'
                  placeholder='Password'
                  className='my-2'
                ></Form.Control>
              </Form.Group>
              <Button className='btn-block mb-3'>Login</Button>
            </Form>
            <Button
              className='btn-danger btn-block'
              onClick={handleGoogleSignIn}
            >
              Sign In with Google
            </Button>
          </div>
        </div>
      </div>
    </Fragment>
  );
};

Home.propTypes = {
  loginGoogle: PropTypes.func.isRequired,
  oauth: PropTypes.string,
};

const mapStateToProps = (state) => ({
  oauth: state.auth.oauth,
});

export default connect(mapStateToProps, { loginGoogle })(Home);
```
### Create Redirect Page
Now it is time to try the application. Once you hit the Sign in with Google button, you will be redirected to the Google Sign In Page. Select an account, then you will see how the application is redirected to the Redirect page. If the authentication goes smoothly, you should see the User page. You can verify in your Parse application that the user is added with the respective authData. A session is also created for that user.

Congratulations, you managed to set up the Google OAuth2 with your Parser Server using a React Application. Please share if you like, and don't hesitate to leave any comments below. Have a good one!