Today we'll learn how to log into our app using OAuth provided by FB & Google.
Today we're going to learn how to implement OAuth authentication, allowing users to sign in to our app using a third-party such as Google or Facebook.
At a high level, this diagram explains the steps we have to follow in order to implement OAuth authentication.
firstName
, lastName
, age
, city
, country
, dob
, description
.npm install react-facebook-login react-google-login
touch .env
.env
REACT_APP_BACKEND_API="http://localhost:5000"
REACT_APP_FB_APP_ID=""
REACT_APP_GOOGLE_CLIENT_ID=""
We haven't defined these environment variables yet, but let's set up our App.js first.
const FB_APP_ID = process.env.REACT_APP_FB_APP_ID;
const GOOGLE_CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID;
RegisterPage
after deleting everything inside of it. <FacebookLogin
appId={FB_APP_ID}
fields="name,email,picture"
callback={loginWithFacebook}
icon="fa-facebook"
onFailure={(err) => {
console.log("FB LOGIN ERROR:", err);
}}
containerStyle={{
textAlign: "center",
backgroundColor: "#3b5998",
borderColor: "#3b5998",
flex: 1,
display: "flex",
color: "#fff",
cursor: "pointer",
marginBottom: "3px",
}}
buttonStyle={{
flex: 1,
textTransform: "none",
padding: "12px",
background: "none",
border: "none",
}}
/>
<GoogleLogin
className="google-btn d-flex justify-content-center"
clientId={GOOGLE_CLIENT_ID}
buttonText="Login with Google"
onSuccess={loginWithGoogle}
onFailure={(err) => {
console.log("GOOGLE LOGIN ERROR:", err);
}}
cookiePolicy="single_host_origin"
/>
App
.const loginWithFacebook = (response) => {
dispatch(authActions.loginFacebookRequest());
};
const loginWithGoogle = (response) => {
dispatch(authActions.loginGoogleRequest());
};
ID
& Secret
.Authorized Origins
& redirect URIS
. Then Grab Client ID
& Secret
.FB_APP_ID
& GOOGLE_CLIENT_ID
and put them inside of our .env
.It should look something like these - do NOT use these variables, but instead use your own.
REACT_APP_FB_APP_ID="1060822310719146x"
REACT_APP_GOOGLE_CLIENT_ID="499012344960-4qodmmb4g8dn94i7q5m14bh2lts4cttk.apps.googleusercontent.comx"
We're front loading the work of installing necessary packages so we don't have to stop and do so later.
npm install passport passport-facebook-token passport-google-token bcryptjs dotenv jsonwebtoken nodemon cors
.env
file and define variables.Here are all the variables we'll need, we get them from Google & Facebook.
PORT=5000
MONGODB_URI="mongodb://localhost:27017/coderbook"
JWT_SECRET_KEY="secretsecret"
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
FACEBOOK_APP_ID=""
FACEBOOK_APP_SECRET=""
./server/app.js
.Passport is the package we'll use to manage authentication more easily.
const passport = require("passport");
require("./middlewares/passport");
app.use(passport.initialize());
./server/middlewares/passport.js
.This is where we configure our authentication strategy.
We define how we'll parse the data coming from Facebook, Google and our frontend app using one of two strategies, FacebookTokenStrategy
, GoogleTokenStrategy
. FacebookTokenStrategyGoogleTokenStrategy
const passport = require("passport");
const FacebookTokenStrategy = require("passport-facebook-token");
const GoogleTokenStrategy = require("passport-google-token").Strategy;
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
const FACEBOOK_APP_ID = process.env.FACEBOOK_APP_ID;
const FACEBOOK_APP_SECRET = process.env.FACEBOOK_APP_SECRET;
const User = require("../models/User");
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use(
new FacebookTokenStrategy(
{
fbGraphVersion: "v3.0",
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
},
function (_, _, profile, done) {
User.findOrCreate(
{
facebookId: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
avatarUrl: profile.photos[0].value,
},
function (error, user) {
return done(error, user);
},
);
},
),
);
passport.use(
new GoogleTokenStrategy(
{
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
},
function (_, _, profile, done) {
User.findOrCreate(
{
googleId: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
avatarUrl: profile._json.picture,
},
function (err, user) {
return done(err, user);
},
);
},
),
);
./server/routes/auth.api.js
and define three endpoints in it. /login
/login/google
/login/facebook
var express = require("express");
var router = express.Router();
const passport = require("passport");
const authController = require("../controllers/auth.controller");
router.post(
"/login/google",
passport.authenticate("google-token", { session: false }),
authController.loginWithFacebookOrGoogle,
);
router.post(
"/login/facebook",
passport.authenticate("facebook-token", { session: false }),
authController.loginWithFacebookOrGoogle,
);
module.exports = router;
Take note of the arguments send to router.post()
.
google-token
or facebook-token
.The different paths result in different authentication strategies. Passport's job is to normalize incoming data. In other words, it makes data consistent when it hits out backend endpoint. Notice all three paths hit the same controller action.
./server/controllers/auth.controller.js
.We import bcrypt
because we use this library to encrypt our users password when they create an account. We also import User
because we use that model to
const bcrypt = require("bcryptjs");
const User = require("../models/User");
const authController = {};
authController.loginWithFacebookOrLogin = async ({ user }, res) => {
if (user) {
user = await User.findByIdAndUpdate(
user._id,
{ avatarUrl: user.avatarUrl },
{ new: true },
);
} else {
throw new Error("There is No User");
}
const accessToken = await user.generateToken();
res.status(200).json({ status: "success", data: { user, accessToken } });
};
module.exports = authController;
./server/models/User.js
.We'll use these packages to secure our app.
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY;
findOrCreate()
on the User
model in ./server/models/User.js
.This function exists on the class User
. Learn more here
userSchema.statics.findOrCreate = function findOrCreate(profile, cb) {
const userObj = new this(); // create a new User class
this.findOne({ email: profile.email }, async function (err, result) {
if (!result) {
let newPassword =
profile.password || "" + Math.floor(Math.random() * 100000000);
const salt = await bcrypt.genSalt(10);
newPassword = await bcrypt.hash(newPassword, salt);
userObj.name = profile.name;
userObj.email = profile.email;
userObj.password = newPassword;
userObj.googleId = profile.googleId;
userObj.facebookId = profile.facebookId;
userObj.avatarUrl = profile.avatarUrl;
userObj.save(cb);
} else {
cb(err, result);
}
});
};