{"resource":{"author":{"id":"h2so9H8jPMmgUKGghoNl","name":"Yomesh Gupta","username":"yomeshgupta"},"content":{"link":null,"difficulty":1,"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 Express then you should check out this [blog-post](https://devtools.tech/blog/add-social-authentication-functionality-to-a-remix-run-project---rid---jVwo6agJvN2oX4IegYgz) where we added Social Auth when our server is Express. In this blog post, we would talk about how we can integrate `Login via Google` with Remix Run project when our server is Remix 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 create a fresh Remix project and choose Remix Server.\r\n\r\n```js\r\nnpx create-remix@latest\r\n# choose Remix 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\nvi .env\r\n```\r\n\r\n```\r\nfile: .env\r\n\r\nNODE_ENV=development\r\nGOOGLE_CLIENT_ID=XXXXXX // use your GOOGLE_CLIENT_ID\r\nGOOGLE_CLIENT_SECRET=XXXXXX // use your GOOGLE_CLIENT_SECRET\r\n```\r\n\r\n2. Install `dotenv` package\r\n\r\n```\r\nnpm i dotenv\r\n```\r\n\r\n3. Update NPM scripts\r\n\r\n```js\r\n\"scripts\": {\r\n  \"dev\": \"node -r dotenv/config ./node_modules/.bin/remix dev\",\r\n  \"start\": \"node -r dotenv/config ./node_modules/.bin/remix-serve build\"\r\n},\r\n```\r\n\r\n## Creating Session Storage\r\n\r\nWe need a session storage object to store the user session. We are going to use the in-built session management utilities provided by Remix.\r\n\r\n```\r\ncd app\r\nmkdir services\r\ntouch session.server.js\r\nvi session.server.js\r\n```\r\n\r\n```js\r\n// file: app/services/session.server.js\r\n\r\nimport { createCookieSessionStorage } from \"remix\";\r\n\r\nexport const sessionStorage = createCookieSessionStorage({\r\n  cookie: {\r\n    name: \"_session\", // use any name you want here\r\n    sameSite: \"lax\", // this helps with CSRF\r\n    path: \"/\", // remember to add this so the cookie will work in all routes\r\n    httpOnly: true, // for security reasons, make this cookie http only\r\n    secrets: [\"s3cr3t\"], // replace this with an actual secret\r\n    secure: process.env.NODE_ENV === \"production\", // enable this in prod only\r\n  },\r\n});\r\n\r\nexport const { getSession, commitSession, destroySession } = sessionStorage;\r\n```\r\n\r\n## Install Dependencies\r\n\r\nFor our use case, we would need two packages:\r\n\r\n- [remix-auth](https://www.npmjs.com/package/@lgastler/remix-auth): It is a complete open-source authentication solution for Remix.run applications. This package is heavily inspired by [Passport.js](https://www.npmjs.com/package/passport) so APIs are quite similar.\r\n- [remix-auth-socials](https://www.npmjs.com/package/remix-auth-socials): This package provides different strategies like `Google`, `Facebook`, `Github` and so on. We can use these social strategies to provide the authentication flow in our app.\r\n\r\n```\r\nnpm i remix-auth remix-auth-socials\r\n```\r\n\r\n## Create Authentication Service\r\n\r\nLet us create a file called `auth.server.js`. In this file, we are going to initialize our `authenticator` and configure different social strategies.\r\n\r\n```\r\ncd app/services\r\ntouch auth.server.js\r\nvi auth.server.js\r\n```\r\n\r\n```js\r\n// file: app/services/auth.server.js\r\n\r\nimport { Authenticator } from \"remix-auth\";\r\nimport { sessionStorage } from \"~/services/session.server\";\r\n\r\n// Create an instance of the authenticator\r\n// It will take session storage as an input parameter and creates the user session on successful authentication\r\nexport const authenticator = new Authenticator(sessionStorage);\r\n```\r\n\r\n### Configure Google OAuth Strategy\r\n\r\n```js\r\n// file: app/services/auth.server.js\r\nimport { GoogleStrategy, SocialsProvider } from \"remix-auth-socials\";\r\n...\r\n\r\n// callback function that will be invoked upon successful authentication from social provider\r\nasync function handleSocialAuthCallback({ profile }) {\r\n  // create user in your db here\r\n  // profile object contains all the user data like image, displayName, id\r\n  return profile;\r\n}\r\n\r\n// Configuring Google Strategy\r\nauthenticator.use(new GoogleStrategy({\r\n    clientID: process.env.GOOGLE_CLIENT_ID,\r\n    clientSecret: process.env.GOOGLE_CLIENT_SECRET,\r\n    scope: [\"openid email profile\"],\r\n    callbackURL: `http://localhost:3000/auth/${SocialsProvider.GOOGLE}/callback`;\r\n  },\r\n  handleSocialAuthCallback\r\n));\r\n```\r\n\r\n### Create Routes that will handle our social login\r\n\r\n1. We are going to create a new route `auth.google.js`. Whenever the user clicks on the `Login with Google` button then a request would be sent to this route and it would initiate the entire authentication flow.\r\n\r\n```\r\ncd app/routes\r\ntouch auth.google.js\r\n```\r\n\r\n```js\r\n// file: app/routes/auth.google.js\r\n\r\nimport { authenticator } from \"../services/auth.server.js\";\r\nimport { SocialsProvider } from \"remix-auth-socials\";\r\n\r\nexport const action = async ({ request }) => {\r\n  // initiating authentication using Google Strategy\r\n  // on success --> redirect to dasboard\r\n  // on failure --> back to homepage/login\r\n  return await authenticator.authenticate(SocialsProvider.GOOGLE, request, {\r\n    successRedirect: \"/dashboard\",\r\n    failureRedirect: \"/\",\r\n  });\r\n};\r\n```\r\n\r\nIn the above code snippet, we export an `action` that would initiate our `Google` strategy authentication flow. If the user is successfully authenticated then it will redirect the user to `/dashboard` else back to the `/` homepage route.\r\n\r\n2. We are going to create a new route `auth.google.callback.js`. This route is the `Authorised redirect URIs` that we provided initially during our app setup. Whenever users are successfully authenticated then Google will send them to this route.\r\n\r\n```\r\ncd app/routes\r\ntouch auth.google.callback.js\r\n```\r\n\r\n```js\r\n// file: app/routes/auth.google.callback.js\r\n\r\nimport { authenticator } from \"../services/auth.server\";\r\nimport { SocialsProvider } from \"remix-auth-socials\";\r\n\r\nexport const loader = ({ request }) => {\r\n  return authenticator.authenticate(SocialsProvider.GOOGLE, request, {\r\n    successRedirect: \"/dashboard\",\r\n    failureRedirect: \"/\",\r\n  });\r\n};\r\n```\r\n\r\n## Create Routes to test the flow\r\n\r\n- Let us add a `Login with Google` button to our homepage.\r\n\r\n```js\r\n// file: app/routes/index.js\r\n\r\nimport { Form } from \"remix\";\r\nimport { SocialsProvider } from \"remix-auth-socials\";\r\n\r\nconst CONTAINER_STYLES = {\r\n  width: \"100%\",\r\n  height: \"100vh\",\r\n  display: \"flex\",\r\n  justifyContent: \"center\",\r\n  alignItems: \"center\",\r\n};\r\n\r\nconst BUTTON_STYLES = {\r\n  padding: \"15px 25px\",\r\n  background: \"#dd4b39\",\r\n  border: \"0\",\r\n  outline: \"none\",\r\n  cursor: \"pointer\",\r\n  color: \"white\",\r\n  fontWeight: \"bold\",\r\n};\r\n\r\nconst IndexPage = () => {\r\n  return (\r\n    <Form\r\n      method=\"post\"\r\n      action={`/auth/${SocialsProvider.GOOGLE}`}\r\n      style={CONTAINER_STYLES}\r\n    >\r\n      <button style={BUTTON_STYLES}>Login with Google</button>\r\n    </Form>\r\n  );\r\n};\r\n\r\nexport default IndexPage;\r\n```\r\n\r\n![Login Button Homepage](https://ik.imagekit.io/devtoolstech/add-social-auth-remix-run/Screenshot_2022-02-10_at_11.46.32_PM_bfcA9EC6C7Q.png?ik-sdk-version=javascript-1.4.3&updatedAt=1644517018537)\r\n\r\n- Now, let us create the `dashboard` route. It will open on successful user authentication.\r\n\r\n```js\r\n// file: app/routes/dashboard.js\r\n\r\nimport { useLoaderData } from \"remix\";\r\n\r\nimport { authenticator } from \"../services/auth.server.js\";\r\n\r\nconst CONTAINER_STYLES = {\r\n  width: \"100%\",\r\n  height: \"100vh\",\r\n  display: \"flex\",\r\n  justifyContent: \"center\",\r\n  alignItems: \"center\",\r\n  flexDirection: \"column\",\r\n};\r\n\r\nconst BUTTON_STYLES = {\r\n  padding: \"15px 25px\",\r\n  background: \"#dd4b39\",\r\n  border: \"0\",\r\n  outline: \"none\",\r\n  cursor: \"pointer\",\r\n  color: \"white\",\r\n  fontWeight: \"bold\",\r\n};\r\n\r\nexport const loader = async ({ request }) => {\r\n  // authenticator.isAuthenticated function returns the user object if found\r\n  // if user is not authenticated then user would be redirected back to homepage (\"/\" route)\r\n  const user = await authenticator.isAuthenticated(request, {\r\n    failureRedirect: \"/\",\r\n  });\r\n\r\n  return {\r\n    user,\r\n  };\r\n};\r\n\r\nconst Dashboard = () => {\r\n  // getting user from loader data\r\n  const { user } = useLoaderData();\r\n\r\n  // displaying authenticated user data\r\n  return (\r\n    <div style={CONTAINER_STYLES}>\r\n      <h1>You are LoggedIn</h1>\r\n      <p>{user.displayName}</p>\r\n    </div>\r\n  );\r\n};\r\n\r\nexport default Dashboard;\r\n```\r\n\r\n![Dashboard Route](https://ik.imagekit.io/devtoolstech/add-social-auth-remix-run/Screenshot_2022-02-10_at_11.51.42_PM_tTfncd-UU.png?ik-sdk-version=javascript-1.4.3&updatedAt=1644517321136)\r\n\r\n## Adding Logout Functionality\r\n\r\nLet us create a route `logout`. When visited, this route is going to delete the user session and log out the user.\r\n\r\n```\r\ncd app/routes\r\ntouch logout.js\r\n```\r\n\r\n```js\r\n// file: app/routes/logout.js\r\n\r\nimport { authenticator } from \"../services/auth.server.js\";\r\n\r\nexport const action = async ({ request }) => {\r\n  await authenticator.logout(request, { redirectTo: \"/\" });\r\n};\r\n```\r\n\r\nLet us update our `Dashboard` route to show a button for the user to log out.\r\n\r\n```js\r\n// file: app/routes/dashboard.js\r\nimport { useLoaderData, Form } from \"remix\";\r\n\r\n...\r\n\r\nconst Dashboard = () => {\r\n  ...\r\n  // displaying authenticated user data\r\n  return (\r\n    <div style={CONTAINER_STYLES}>\r\n      ...\r\n      <Form action=\"/logout\" method=\"post\">\r\n        <button style={BUTTON_STYLES}>Logout</button>\r\n      </Form>\r\n    </div>\r\n  );\r\n};\r\n\r\n...\r\n```\r\n\r\n![Dashboard with Logout Button](https://ik.imagekit.io/devtoolstech/add-social-auth-remix-run/Screenshot_2022-02-11_at_12.11.53_AM_iq4PWcX9U8h.png?ik-sdk-version=javascript-1.4.3&updatedAt=1644518527164)\r\n\r\nNow, when the user clicks on the `Logout` button then form submission would happen and a request would be made to `/logout` that would eventually delete the user session!\r\n\r\n## Demo\r\n\r\n\r\n<video width=\"100%\" height=\"100%\" controls>\r\n  <source src=\"https://ik.imagekit.io/devtoolstech/add-social-auth-remix-run/social-auth-demo_xBbVBLizp.mp4?ik-sdk-version=javascript-1.4.3&updatedAt=1644520540465\" type=\"video/mp4\">\r\n</video>\r\n\r\n## Live Demo\r\n\r\nYou can check out the live demo here -- [https://devtools.tech/login](https://devtools.tech/login/?ref=social-auth-remix-server-blog-post)\r\n\r\n## Source Code\r\n\r\nYou can find the entire source code for this tutorial here: [https://github.com/Devtools-Tech-Team/remix-run-social-authentication-example](https://github.com/Devtools-Tech-Team/remix-run-social-authentication-example)\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":18871,"used":0,"likes":0},"description":"","published":true,"isActive":true,"tags":["javascript","remix run","social login","login with google","session","encryption","js fundamentals","remix server"],"slug":"add-social-authentication-to-remix-run-project-or-remix-server---rid---GEN4IbgWorJNeQL7Gkm8","isPremium":false,"categories":[],"requires":[],"_id":"62056636e12bce537863f36a","title":"Add Social Authentication to Remix Run Project | Remix Server","resourceId":"GEN4IbgWorJNeQL7Gkm8","createdAt":1644521014315,"modifiedAt":1644521372553},"currentUser":null,"isOwner":false,"recommendations":{"questions":[{"_id":"6431a5916577005718b8b399","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["javascript","frontend","code","programming","interview","linkedin","frontend interview question","javascript interview question","tuple","js tuple","row","arrays","js arrays","nested arrays"],"slug":"implement-tuple-function-or-linkedin-frontend-interview-question-or-javascript-problem-solving---qid---hr6hxjztrf5BmneXlUtT","title":"Implement Tuple Function | LinkedIn Frontend Interview Question | JavaScript Problem Solving","questionId":"hr6hxjztrf5BmneXlUtT"},{"_id":"692ed637697e24762d33bf74","content":{"languages":["javascript","typescript"],"difficulty":2},"tags":["javascript","coding","ui","ux","algorithms","data structures","frontend"],"slug":"identical-dom-trees---qid---uGJkyIch5BHsO71axwsY","title":"Identical DOM Trees","questionId":"uGJkyIch5BHsO71axwsY"},{"_id":"652ac1efd5ab2876a4c9e7a1","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["frontend","object","comparison","ui","ux","deep equal","lodahs","frontend interview question","devtools tech","blog","javascript interview question","isEqual","deep comparison","recursion","objects","problem solving","lodash","polyfill"],"slug":"how-to-check-deep-equality-between-javascript-objects-or-zeta-frontend-interview-question---qid---1YjgIig2dBwqkpovXNrR","title":"How to check deep equality between JavaScript objects? | Zeta Frontend Interview Question","questionId":"1YjgIig2dBwqkpovXNrR"},{"_id":"6919ae3bbf1a48f85e0910f4","content":{"languages":["react"],"difficulty":1},"tags":["javascript","ui","ux","razorpay frontend interview question","coding","problem solving","interview question","dropdown component","search"],"slug":"reusable-dropdown-component-with-search---qid---h1IRuH69b5ckioqUugrt","title":"Reusable Dropdown Component with Search","questionId":"h1IRuH69b5ckioqUugrt"},{"_id":"63a97bea22480f26b3cbb6ad","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["javascript","problem solving","interview question","interview preparation","function","toggle functionality","vanilla js","basic js","facebook interview","netflix","google interview questions","frontend interview questions"],"slug":"how-to-create-a-toggle-function-in-javascript-or-frontend-problem-solving-or-javascript-interview-question---qid---gbucvY2jab4e1q6Fo6iV","title":"How to create a toggle function in JavaScript? | Frontend Problem Solving | JavaScript Interview Question","questionId":"gbucvY2jab4e1q6Fo6iV"}],"resources":[{"_id":"5f202c81cbec5f7ffc0c2fba","content":{"difficulty":1,"domain":2,"type":1},"tags":["Web Development"],"slug":"script-tag-—-defer-and-async---rid---NCXH57kUsnTM2Ev2tzEU","title":"Script Tag — Defer and Async","resourceId":"NCXH57kUsnTM2Ev2tzEU"},{"_id":"5f2c0c39f81d6d48b5c4cd06","content":{"difficulty":1,"domain":2,"type":1},"tags":["frontend","html","css","webpage performance","css3","page load times"],"slug":"content-visibility-the-new-css-property-that-boosts-your-rendering-performance---rid---qB5R8n3HZ3NWdukfs7zu","title":"content-visibility: the new CSS property that boosts your rendering performance","resourceId":"qB5R8n3HZ3NWdukfs7zu"},{"_id":"6963e277132ce4e954b04414","content":{"difficulty":4,"domain":2,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","ui","ux","devtools tech","coding","frontend performance","browser rendering cycle"],"slug":"mastering-critical-rendering-path---rid---VyhOczhtA0Ds1kp2s4TN","title":"Mastering Critical Rendering Path","resourceId":"VyhOczhtA0Ds1kp2s4TN"},{"_id":"652d65f9d5ab2876a4ca215c","content":{"difficulty":4,"domain":2,"type":1,"isInternal":false,"languages":["undefined"]},"tags":["frontend","coding","interview","tooling","web tools","ui","ux","atlan","devtools tech"],"slug":"atlan-interview-experience-frontend-engineer-intern---rid---v5soIECQ4OQQ9S1FFR7n","title":"Atlan Interview Experience: Frontend Engineer Intern","resourceId":"v5soIECQ4OQQ9S1FFR7n"},{"_id":"5f2af665f81d6d48b5c4cd05","content":{"difficulty":2,"domain":16,"type":2},"tags":["data structures","algorithms","dynamic programming","interviews","interview preparation","code","programming","backend","frontend"],"slug":"dynamic-programming-playlist-or-coding-or-interview-questions-or-tutorials-or-algorithm---rid---Gk2BameUXnvutf6jDwCA","title":"Dynamic Programming Playlist | Coding | Interview Questions | Tutorials | Algorithm","resourceId":"Gk2BameUXnvutf6jDwCA"}]}}