{"resource":{"author":{"id":"h2so9H8jPMmgUKGghoNl","name":"Yomesh Gupta","username":"yomeshgupta"},"content":{"link":null,"difficulty":4,"domain":1,"type":1,"isInternal":true,"body":"Social Login is a form of single sign-on using existing information from a social network provider like Google, Facebook, Twitter, Github, and so on. \r\n\r\nIf your app server is Remix Server then you should check out this [blog-post](https://devtools.tech/blog/add-social-authentication-to-remix-run-project-or-remix-server---rid---GEN4IbgWorJNeQL7Gkm8) where we added Social Auth when our server is Remix Server. In this blog post, we would talk about how we can integrate `Login via Google` with Remix Run project when our server is Express Server!\r\n\r\n## Setting up an app in Google Developer Console and getting GOOGLE_CLIENT_ID and GOOLGE_CLIENT_SECRET\r\n\r\nBefore we start any development, first we need to set up an app in Google Developer Console and get the necessary credentials.\r\n\r\n1. Go to [https://console.cloud.google.com/](https://console.cloud.google.com/)\r\n2. Select a project or create a new one\r\n3. Once the project is selected, go to [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials)\r\n4. Open the Credentials tab from the sidebar\r\n5. Click on `Create Credentials` and select `OAuth client ID`\r\n6. Select Application type as `Web application`.\r\n7. Name your application. For e.g. `LoginApp`\r\n8. In the `Authorised redirect URIs` section, click on `ADD URI` and add `http://localhost:3000/auth/google/callback`\r\n9. Click on `CREATE`\r\n10. You will now get your `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`. Copy and save them securely.\r\n\r\n## Create a Remix Run Project\r\n\r\nWe are going to use [passport.js](https://www.passportjs.org/) to provide authentication functionality. It is a middleware for Node.js and works well with Express-based web applications. So, we are going to create a fresh Remix project and choose Express as our server.\r\n\r\n```js\r\nnpx create-remix@latest\r\n# choose Express Server\r\ncd [whatever you named the project]\r\n```\r\n\r\n## Load Secrets as ENVIRONMENT VARIABLES\r\n\r\n1. Let us first create a `.env` file to store the `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` downloaded earlier.\r\n\r\n```\r\ntouch .env\r\n```\r\n\r\n2. Install the `dotenv` package to load these variables into our app.\r\n\r\n```\r\nnpm i dotenv\r\n```\r\n\r\n3. Update our Server File\r\n\r\n```js\r\n// file: server/index.js\r\n\r\n// this loads the `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` as environment variables\r\n// that can be accessed via\r\n// process.env.GOOGLE_CLIENT_ID\r\n// process.env.GOOGLE_CLIENT_SECRET\r\nrequire(\"dotenv\").config();\r\n```\r\n\r\n## Install Dependencies\r\n\r\nLet us install the required packages from npm.\r\n\r\n1. `PassportJS`\r\n2. `passport-google-oauth2` - This is the Google Strategy Library that we are going to use to provide `Login with Google` functionality.\r\n\r\n```\r\nnpm i passport passport-google-oauth2\r\n```\r\n\r\n## Initializing Passport\r\n\r\n```js\r\n// file: server/index.js;\r\n\r\nconst passport = require(\"passport\");\r\n\r\napp.use(passport.initialize());\r\n```\r\n\r\n## Configuring Strategy\r\n\r\n```js\r\n// file: server/index.js\r\n...\r\nconst GoogleStrategy = require(\"passport-google-oauth2\").Strategy;\r\n\r\nasync function handleStrategyCallback(request, accessToken, refreshToken, profile, done) {\r\n  // Use profile data and token to create a user profile in your system\r\n  // profile object will have data like\r\n  // id, emails, displayName, photos, provider, profileUrl\r\n\r\n  // Something like\r\n  const user = await User.findOrCreate({ googleId: profile.id });\r\n\r\n  // once user is stored in your db\r\n  return done(null, user);\r\n}\r\n\r\npassport.use(\r\n  new GoogleStrategy(\r\n    {\r\n      clientID: process.env.GOOGLE_CLIENT_ID,\r\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET,\r\n      callbackURL: \"http://localhost:3000/auth/google/callback\", // change the domain to your domain in production\r\n      scope: [\"openid email profile\"],\r\n      passReqToCallback: true,\r\n    },\r\n    handleStrategyCallback\r\n  )\r\n);\r\n...\r\n```\r\n\r\n## Implementing serializeUser() and deserializeUser()\r\n\r\nPassport uses the `serializeUser` function to persist user data (after successful authentication) and function `deserializeUser` is used to retrieve the user data.\r\n\r\n```js\r\n// file: server/index.js;\r\n\r\n// user object is the authenticated user that we get from `handleStrategyCallback` function\r\npassport.serializeUser((user, done) => {\r\n  done(null, user);\r\n});\r\n\r\n// user object is the authenticated user that we get from `serializeUser` function\r\npassport.deserializeUser((user, done) => {\r\n  done(null, user);\r\n});\r\n```\r\n\r\n## Setting up request URLs\r\n\r\nFirst, we are going to handle the route when the request initially comes to our backend i.e. when the user clicks on the `Login with Google` button then call a is made to our back-end. It would be the starting point for the social auth flow.\r\n\r\n```js\r\n// file: server/index.js\r\n\r\napp.use(\"/auth/google/\", (req, res, next) => {\r\n  // invoke passport authentication function with google strategy\r\n  return passport.authenticate(\"google\", {\r\n    failureRedirect: \"/error\",\r\n  })(req, res, next);\r\n});\r\n```\r\n\r\nNow, the user would be redirected to the Google Sign-in screen where they can log in to their account and allow our app. Once that is done then our `handleStrategyCallback` function would be invoked with all the necessary information provided by Google and we save the user to our database. A call is made to the `Authorised URI` we provided during our initial project setup.\r\n\r\n```js\r\napp.use(\"/auth/google/callback\", (req, res, next) => {\r\n  // callback from Google\r\n  return passport.authenticate(\"google\", {\r\n    failureRedirect: \"/error\",\r\n  })(req, res, next);\r\n});\r\n```\r\n\r\n## Handling User Session\r\n\r\n1. Since, we are using an Express server, we can manage the user session on our own using packages like [express session](https://www.npmjs.com/package/express-session). If we go that route then we have to make some changes to let passport manage the session.\r\n\r\n```js\r\n// file: server/index.js\r\n\r\nconst session = require(\"express-session\");\r\n\r\napp.use(\r\n  session({\r\n    secret: \"secret\",\r\n    resave: false,\r\n    saveUninitialized: true,\r\n  })\r\n);\r\n\r\napp.use(passport.session());\r\n```\r\n\r\nNow, we will have `req.session.passort.user` that will contain our authenticated user object. We can use this information to perform check on routes access.\r\n\r\n2. We can use Remix's built-in session management utilities to manage the session.\r\n\r\n### Remix Server Side Session Handling\r\n\r\nLet us create session handling using Remix built-in utilities.\r\n\r\n```\r\ncd app\r\ntouch session.server.js\r\n```\r\n\r\n```js\r\n// file: app/session.server.js\r\n\r\nimport { createCookieSessionStorage, redirect } from \"remix\";\r\n\r\n// loads this from .env file in production\r\nconst sessionSecret = \"secret\";\r\n\r\nconst storage = createCookieSessionStorage({\r\n  cookie: {\r\n    name: \"cookie_name\",\r\n    secure: true,\r\n    secrets: [sessionSecret],\r\n    sameSite: \"lax\",\r\n    path: \"/\",\r\n    maxAge: 60 * 60 * 24 * 30,\r\n    httpOnly: true,\r\n  },\r\n});\r\n\r\nexport async function createUserSession(userId, redirectTo) {\r\n  const session = await storage.getSession();\r\n  session.set(\"userId\", userId);\r\n\r\n  return redirect(redirectTo, {\r\n    headers: {\r\n      \"Set-Cookie\": await storage.commitSession(session),\r\n    },\r\n  });\r\n}\r\n```\r\n\r\nIn the above code snippet, we created a function `createUserSession` that takes `userId` as a parameter and creates the session for us. However, we don't have userId. We need to pass that userId from our express route handling to Remix securely.\r\n\r\nWe can do so by creating an API route in Remix server code and passing the `userId` to it as a cookie from our `/auth/google/callback` request. To securely pass it, we will use a package [string-crypto](https://www.npmjs.com/package/string-crypto) to encrypt and decrypt the cookie value.\r\n\r\n```\r\nnpm i string-crypto\r\n```\r\n\r\n```js\r\nconst StringCrypto = require(\"string-crypto\");\r\n\r\nconst { encryptString } = new StringCrypto();\r\n\r\nfunction handleSocialLoginCallback(req, res, next) {\r\n  // change the secret to something more secure\r\n  // store it as env variable\r\n  const encryptedUserId = encryptString(req.user.userId, \"secret\");\r\n\r\n  res.cookie(\"cookie-name\", encryptedUserId, {\r\n    sameSite: \"lax\",\r\n    path: \"/\",\r\n    maxAge: 60 * 60 * 24 * 30,\r\n    httpOnly: true,\r\n  });\r\n\r\n  return res.redirect(`http://localhost:3000/api/users/auth`);\r\n}\r\n\r\napp.use(\r\n  \"/auth/google/callback\",\r\n  (req, res, next) => {\r\n    // callback from Google\r\n    return passport.authenticate(\"google\", {\r\n      failureRedirect: \"/error\",\r\n    })(req, res, next);\r\n  },\r\n  handleSocialLoginCallback\r\n);\r\n```\r\n\r\n### Create Remix Route and session creation\r\n\r\n```\r\ncd app/routes\r\ntouch api.users.auth.js\r\n```\r\n\r\n```js\r\n// file: api.users.auth.js\r\nimport { redirect } from \"remix\";\r\nimport StringCrypto from \"string-crypto\";\r\n\r\nimport { createUserSession } from \"../app/session.server\";\r\n\r\nconst { decryptString } = new StringCrypto();\r\n\r\nexport const loader = async ({ request }) => {\r\n  const cookies = request.headers.get(\"Cookie\") || \"\";\r\n  const cookiesArray = cookies.split(\";\");\r\n  const userCookie = cookiesArray.find((cookie) =>\r\n    cookie.includes(\"cookie-name\")\r\n  );\r\n\r\n  if (!userCookie) {\r\n    return redirect(\"/error\");\r\n  }\r\n\r\n  let encryptedUserId = userCookie.split(\"=\")[1];\r\n  encryptedUserId = decodeURIComponent(userId);\r\n\r\n  const userId = decryptString(encryptedUserId, \"secret\");\r\n\r\n  return await createUserSession(userId, \"/dashboard\");\r\n};\r\n```\r\n\r\nIn the above code snippet, we read the cookie from the request headers. Decoded it using the secret, created the user session, and redirected the logged-in user to the dashboard.\r\n\r\n## Handling Logout\r\n\r\nLet us add a function to logout users that delete the session using the `storage` object we created earlier.\r\n\r\n```js\r\n// file: app/session.server\r\n...\r\n\r\nexport async function logout(request) {\r\n  const session = await storage.getSession(request.headers.get(\"Cookie\"));\r\n\r\n  return redirect(\"/login\", {\r\n    headers: {\r\n      \"Set-Cookie\": await storage.destroySession(session),\r\n    },\r\n  });\r\n}\r\n```\r\n\r\nNow, when we receive a request to `GET /logout` then we can call the above-created function and delete the user session.\r\n\r\n## See the entire flow live here -- [https://devtools.tech/login](https://devtools.tech/login?ref=social-auth-blog-post)\r\n\r\nI hope this blog post helped you in some way. Please do share it and show our content much needed love! :D\r\n\r\nIf you feel something is missing/wrong/improvement is possible then feel free to reach out -- [Devtools Tech](https://twitter.com/devtoolstech) and [Yomesh Gupta](https://twitter.com/yomeshgupta)\r\n","languages":[],"editorConfig":{}},"stats":{"views":15288,"used":0,"likes":0},"description":"","published":true,"isActive":true,"tags":["javascript","remix run","social login","login with google","express","session","encryption","js fundamentals"],"slug":"add-social-authentication-to-remix-run-project-or-express-server---rid---jVwo6agJvN2oX4IegYgz","isPremium":false,"categories":[],"requires":[],"_id":"620406cce12bce537863eaf0","title":"Add Social Authentication to Remix Run Project | Express Server","resourceId":"jVwo6agJvN2oX4IegYgz","createdAt":1644431052479,"modifiedAt":1644521538658},"currentUser":null,"isOwner":false,"recommendations":{"questions":[{"_id":"643692fa6577005718b9389e","content":{"languages":["react"],"difficulty":1},"tags":["javascript","frontend","react hooks","custom hook","componentdidupdate","componentdidmount","react lifecycle methods","useDidUpdate","frontend interview question","react interview question","problem solving"],"slug":"how-to-mimic-componentdidupdate-using-react-hooks-usedidupdate-custom-hook-or-react-interview-question-or-javascript---qid---sPSyC69Gdd8dIaGJtbUl","title":"How to mimic componentDidUpdate using React Hooks? useDidUpdate Custom Hook | React Interview Question | JavaScript","questionId":"sPSyC69Gdd8dIaGJtbUl"},{"_id":"64e5c934d5ab2876a4c2a515","content":{"languages":["javascript","typescript"],"difficulty":2},"tags":["","javascript","frontend","youtube","camel case","problem solving","iteration","recursion","devtools tech","frontend interview questions","array","objects"],"slug":"implement-a-function-to-convert-all-object-keys-to-camel-case-or-javascript-interview-question---qid---9E4Ju40HMV36ksRPq9oF","title":"Implement a function to convert all object keys to camel case | JavaScript Interview Question","questionId":"9E4Ju40HMV36ksRPq9oF"},{"_id":"6878f2a950393ab55083191e","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["javascript","objects","frontend interview questions","ui","ux","devtools tech","coding","tutorial","blog","zepto coding challenge"],"slug":"implement-listento-function---qid---JqRACUtqsOONwFUNREXb","title":"Implement listenTo function","questionId":"JqRACUtqsOONwFUNREXb"},{"_id":"68f0a6a1e901f595bb24c2ab","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["javascript","ui","ux","some","array","polyfills","devtools tech","programming question","coding question"],"slug":"how-to-implement-custom-array-prototype-some-method---qid---sdnHxbS34ijKiIoL8vmJ","title":"How to implement custom Array.prototype.some method?","questionId":"sdnHxbS34ijKiIoL8vmJ"},{"_id":"5f382faa6d3cda64e470c3da","content":{"difficulty":1,"languages":"javascript"},"tags":["javascript","node.js","js fundamentals","in keyword","emcascript","es6"],"slug":"what-will-be-the-output-of-the-following-code-snippet-in-operator---qid---7fgg7ORoySqruguvpaGG","title":"What will be the output of the following code snippet? (in operator)","questionId":"7fgg7ORoySqruguvpaGG"}],"resources":[{"_id":"6341cada42e7761bfac1a074","content":{"difficulty":2,"domain":2,"type":2,"isInternal":false},"tags":["frontend","devtools tech","tutorials","eslint","custom eslint rules","webpack","code commit","pre commit","commit hook","tooling","developer experience","remix","code masters","codechef","frontend masters","codedamn","egghead"],"slug":"how-to-improve-your-codebase-or-custom-eslint-rules-or-advanced-javascript-or-devtools-tech---rid---KgSipm8RngCFwwtI7bh2","title":"How to Improve Your Codebase!? | Custom ESLint Rules | Advanced JavaScript | Devtools Tech","resourceId":"KgSipm8RngCFwwtI7bh2"},{"_id":"5f62fc016d3cda64e470c3f3","content":{"difficulty":4,"domain":2,"type":2},"tags":["javascript","closures","frontend","js fundamentals","devtools","interview concept"],"slug":"javascript-closures-or-lexical-environment-or-interview-question-or-saloni-kathuria---rid---OlEK5KKN3yZeASgPJfTm","title":"JavaScript Closures | Lexical Environment | Interview Question | Saloni Kathuria","resourceId":"OlEK5KKN3yZeASgPJfTm"},{"_id":"692e9c62697e24762d339f5c","content":{"difficulty":1,"domain":2,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","ui","ux","devtools tech","tutorial","react","rendering","mount","lifecycle"],"slug":"re-rendering-vs-unmount-in-react---rid---JUT1eFkORr5oUDT2lXtc","title":"Re-rendering vs Unmount in React","resourceId":"JUT1eFkORr5oUDT2lXtc"},{"_id":"61f104fcf144a82a5ee29fb2","content":{"difficulty":1,"domain":2,"type":1,"isInternal":true},"tags":["javascript","hoisting","interview","js fundamentals","frontend fundamentals","scoping","temporal dead zone","frontend interview questions","interview answers"],"slug":"hoisting-in-javascript-explained-or-javascript-interview-questions---rid---6oy63tirhGHUyvC3HYf1","title":"Hoisting in JavaScript Explained | JavaScript Interview Questions","resourceId":"6oy63tirhGHUyvC3HYf1"},{"_id":"639222ff937c6f54916670ee","content":{"difficulty":3,"domain":2,"type":2,"isInternal":false},"tags":["frontend","javascript","react","interview question","coding","programming","custom hooks","react hooks","ui","ux","blog","tech"],"slug":"how-to-create-useprevious-react-custom-hook-or-advanced-react-js-or-javascript-interview-question---rid---Dwblxb3EWYYxTG2vmwEp","title":"How to create usePrevious React Custom Hook? | Advanced React.js | JavaScript Interview Question","resourceId":"Dwblxb3EWYYxTG2vmwEp"}]}}