DEV Community

Cover image for How to Setup Google OAuth2 Login with Parse Server in React
Autodidaktum
Autodidaktum

Posted on

How to Setup Google OAuth2 Login with Parse Server in React

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:

const user = new Parse.User();
await user.linkWith('google', { authData: myAuthData });
Enter fullscreen mode Exit fullscreen mode

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. A sample application is also hosted at Back4App.

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:

{ 
    "dependencies": {
        "googleapis": "^61.0.0"
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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;
});
Enter fullscreen mode Exit fullscreen mode

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:

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;
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

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:

import Parse from 'parse'

Parse.initialize(APPLICATION_ID, JAVASCRIPT_KEY);
Parse.serverURL = SERVER_URL;
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

Add another folder inside redux called reducers, and create a file index.js. Then, add the necessary code to configure your root reducer

import { combineReducers } from "redux";

export default combineReducers({});
Enter fullscreen mode Exit fullscreen mode

Finally, go back to App.js and add the Store Provider. At the end, App.js should look like this

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;
Enter fullscreen mode Exit fullscreen mode

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

export const REDIRECT_SUCCESS = "REDIRECT_SUCCESS";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const AUTH_ERROR = "AUTH_ERROR";
Enter fullscreen mode Exit fullscreen mode

Under auth.js, let's define the actions to handle the Google Sign Up:

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,
      });
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Now, let's create our auth reducer. Here we define how the store will keep the User Information and Authentication Data:

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to include your auth reducer in your combine reducer at index.js

import { combineReducers } from "redux";
import auth from "./auth";

export default combineReducers({
  auth,
});
Enter fullscreen mode Exit fullscreen mode

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:

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);
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
 
mrchedda profile image
mr chedda

Thanks for this! Though this is incomplete, getTokenGoogle() is defined but is never shown how it's called. What are the params passed to the function? Any help would be appreciated. Thanks