{"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":19462,"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":"6015b6a04ddda40a64cf14f8","content":{"languages":["javascript","typescript"],"difficulty":4},"tags":["javascript","frontend","arrays","interview","recursion","prototype chain","advanced javascript","arrow functions"],"slug":"how-to-create-a-flat-version-of-a-deeply-nested-array-programming-interview-question---qid---l5Qx4LJ7glelCkIxQjXB","title":"How to create a flat version of a deeply nested array? [Programming Interview Question]","questionId":"l5Qx4LJ7glelCkIxQjXB"},{"_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":"645b815c6577005718bc9ed7","content":{"languages":["javascript","typescript"],"difficulty":1},"tags":["frontend","code","programming","lodash","polyfill","lodash once","javascript interview question","ui","ux","devtools tech","codedamn","egghead","frontend masters","problem solving","frontend interview questions","underscore","geeksforgeeks"],"slug":"implement-a-function-that-accepts-a-callback-and-restricts-its-invocation-to-at-most-once-or-lodash-polyfills-or-frontend-problem-solving---qid---RLIaoJTXeiN81W2HQYry","title":"Implement a function that accepts a callback and restricts its invocation to at most once | Lodash Polyfills | Frontend Problem Solving","questionId":"RLIaoJTXeiN81W2HQYry"},{"_id":"5eca301cbe633f3afec7c68f","content":{"languages":["javascript"],"difficulty":2},"tags":["node.js","javascript","promises","frontend","frontend fundamentals","js fundamentals","interview questions"],"slug":"what-would-be-the-output-of-the-following-code-snippet-or-promise-based-output-question-or-part-two---qid---mxk2aiuSwTNwpApt1L4c","title":"What would be the output of the following code snippet? | Promise Based Output Question | Part Two","questionId":"mxk2aiuSwTNwpApt1L4c"},{"_id":"6215e6591641361c7c65de90","content":{"difficulty":3,"languages":"javascript"},"tags":["javascript","frontend","coding","architecture","js fundamentals","web","system design"],"slug":"how-would-you-implement-pagination-in-a-frontend-application-or-javascript-interview-questions-or-frontend-architecture---qid---IL8Whw2ayeqmg0kvHQLd","title":"How would you implement pagination in a frontend application? | JavaScript Interview Questions | Frontend Architecture","questionId":"IL8Whw2ayeqmg0kvHQLd"}],"resources":[{"_id":"69b3e73392f33c7d8ab0e2bd","content":{"difficulty":4,"domain":2,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","ui","ux","devtools tech","interview experiences","frontend coding"],"slug":"cars24-frontend-interview-experience---rid---5CxS7tyPLqunm9n3xIfH","title":"Cars24 Frontend Interview Experience","resourceId":"5CxS7tyPLqunm9n3xIfH"},{"_id":"652d6531d5ab2876a4ca214e","content":{"difficulty":4,"domain":2,"type":1,"isInternal":false,"languages":["undefined"]},"tags":["frontend interview","amazon","medium","devtools tech","blog","article","tutorial"],"slug":"how-was-my-frontend-engineer-interview-experience-amazon---rid---fProKruFZQXgvEgZffz9","title":"How was my Frontend Engineer Interview Experience @ Amazon","resourceId":"fProKruFZQXgvEgZffz9"},{"_id":"698b2bde895fc3bf01415e40","content":{"difficulty":4,"domain":2,"type":1,"isInternal":true,"languages":[]},"tags":["javascript","ui","ux","coding","frontend","devtools tech","frontend interview experience"],"slug":"zomato-frontend-interview-experience-or-sde-2---rid---7hUoqLgUfYf13XaOXRlq","title":"Zomato Frontend Interview Experience | SDE 2","resourceId":"7hUoqLgUfYf13XaOXRlq"},{"_id":"5f1ffa3acbec5f7ffc0c2fb7","content":{"difficulty":3,"domain":2,"type":1},"tags":["javascript","frontend","advanced react","build react","react internals","tutorials","reactjs"],"slug":"build-your-own-react---rid---XdoSkFr67KA5TMwu1lc0","title":"Build your own React","resourceId":"XdoSkFr67KA5TMwu1lc0"},{"_id":"620406cce12bce537863eaf0","content":{"difficulty":4,"domain":1,"type":1,"isInternal":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","title":"Add Social Authentication to Remix Run Project | Express Server","resourceId":"jVwo6agJvN2oX4IegYgz"}]}}