{"id":17683199,"url":"https://github.com/nemanjam/react-material-passport","last_synced_at":"2025-05-12T23:51:11.376Z","repository":{"id":102334905,"uuid":"181913641","full_name":"nemanjam/react-material-passport","owner":"nemanjam","description":"Authentication boilerplate with React, Material-UI and Local, Facebook, Google and JWT Passport strategies.","archived":false,"fork":false,"pushed_at":"2020-08-20T15:43:28.000Z","size":201,"stargazers_count":6,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-01T04:51:14.619Z","etag":null,"topics":["authentication","boilerplate","express","material-ui","mongodb","passport","reactjs"],"latest_commit_sha":null,"homepage":"https://react-material-passport.herokuapp.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nemanjam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"security/req.cnf","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-17T14:55:33.000Z","updated_at":"2021-10-27T03:47:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"4d3b28a4-fa5d-45db-b579-bcf203e3857d","html_url":"https://github.com/nemanjam/react-material-passport","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemanjam%2Freact-material-passport","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemanjam%2Freact-material-passport/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemanjam%2Freact-material-passport/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nemanjam%2Freact-material-passport/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nemanjam","download_url":"https://codeload.github.com/nemanjam/react-material-passport/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253843167,"owners_count":21972868,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","boilerplate","express","material-ui","mongodb","passport","reactjs"],"created_at":"2024-10-24T09:44:39.056Z","updated_at":"2025-05-12T23:51:11.349Z","avatar_url":"https://github.com/nemanjam.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Material Passport\n\n## Features\n\n- React with JWT authentication with Redux Thunk and Material UI\n- Express with Mongoose\n- Passport with Local, JWT, Facebook and Google strategies\n\n## Demo  \n\nDemo **[here](https://react-material-passport.herokuapp.com)**.  \n\n## Screenshots\n\n![Screenshot1](/screenshots/Screenshot_1.png)\n\n## Installation\n\nInstall backend dependencies with:\n\n```\nnpm install\n```\n\nInstall client dependencies with:\n\n```\ncd client\nnpm install\n```\n\nIn the `/config` folder you need to create `dev.js` config file with the following:\n\n```javascript\nmodule.exports = {\n  mongoURI: \"mongodb://localhost:27017/react-material-passport-path-to-db\",\n  googleClientID: \"your google client id\",\n  googleClientSecret: \"your google secret\",\n  googleCallbackURL: \"/auth/google/callback\",\n  facebookAppID: \"your facebook app id\",\n  facebookSecret: \"your facebook secret\",\n  facebookCallbackURL: \"/auth/facebook/callback\",\n  secretOrKey: \"secret string for jwt\",\n  successRedirectURL: \"https://localhost:3000\"\n};\n```\n\nCreate certificates with:\n\n```\ncd security\nopenssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout cert.key -out cert.pem -config req.cnf -sha256\n```\n\nThen run the nodemon server with:\n\n```\nnpm run server\n```\n\nThe server will be available on `https://localhost:5000`\n\nRun the client with:\n\n```\ncd client\nnpm run start\n```\n\nThe client will be available on `https://localhost:3000`\n\nor run the both with:\n\n```\nnpm run dev\n```\n\n## Backend\n\nFor Facebook OAuth to work it requires https on local server so we make use of built in https server with:\n\n```javascript\nconst port = process.env.PORT || 5000;\n\nconst httpsOptions = {\n  key: fs.readFileSync(\"./security/cert.key\"),\n  cert: fs.readFileSync(\"./security/cert.pem\")\n};\n\nconst server = https.createServer(httpsOptions, app).listen(port, () =\u003e {\n  console.log(\"https server running at \" + port);\n});\n```\n\nAs you can see for this to work we must generate `cert.key` and `cert.pem` certificates in `security` folder, we do that by navigating to `/security` and executing:\n\n```\nopenssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout cert.key -out cert.pem -config req.cnf -sha256\n```\n\nIn the `/security/req.cnf` put something like this:\n\n```\n[req]\ndistinguished_name = req_distinguished_name\nx509_extensions = v3_req\nprompt = no\n[req_distinguished_name]\nC = GE\nST = State\nL = Location\nO = Organization Name\nOU = Organizational Unit\nCN = www.localhost.com\n[v3_req]\nkeyUsage = critical, digitalSignature, keyAgreement\nextendedKeyUsage = serverAuth\nsubjectAltName = @alt_names\n[alt_names]\nDNS.1 = www.localhost.com\nDNS.2 = localhost.com\nDNS.3 = localhost\n```\n\nNow we can specify `https://localhost:5000/auth/facebook/callback` for callback url in Facebook app settings.\n\nWe also mount two static folders, one `/static` for the images and other assets needed by React, the other one `/client/build` is used in the production is for serving React's assets and for the all other routes we return `/client/build/index.html` and let the React do the routing.\n\n```javascript\n// Use Routes\napp.use(\"/\", authRoutes);\napp.use(\"/\", apiRoutes);\napp.use(\"/static\", express.static(__dirname + \"/static\"));\n\n// Serve static assets if in production\nif (process.env.NODE_ENV === \"production\") {\n  // Set static folder\n  app.use(express.static(\"client/build\"));\n\n  app.get(\"*\", (req, res) =\u003e {\n    res.sendFile(path.resolve(__dirname, \"client\", \"build\", \"index.html\"));\n  });\n...\n```\n\nWe make use of https server only in development because for example Heroku wont let us run it on a free plan on the port 5000 so in the production we does the usual on port 80. And it is accessible on https url by default so we can have https callback url.\n\n```javascript\nconst port = process.env.PORT || 80;\napp.listen(port, () =\u003e console.log(`Server started on port ${port}`));\n```\n\n### Passport configuration\n\nIn the `/services` folder we have all the Passport strategies, only the local strategy does flashing the error messages. It does it without session, we don't use session because we use JWT token in the `x-auth-token` header to auth API calls. For the flashing messages to work we make custom callback in `middleware/requireLocalAuth.js`:\n\n```javascript\nconst requireLocalAuth = function(req, res, next) {\n  passport.authenticate(\"local\", function(err, user, info) {\n    if (err) {\n      return next(err);\n    }\n    if (!user) {\n      return res.status(422).send(info); // flash the error message\n    }\n    req.user = user;\n    next();\n  })(req, res, next);\n};\n```\n\nAfter user signs in Facebook redirects him to the specified callback route from which we have to both redirect and generate and send JWT token. We do that by sending token via cookie and redirect to home page. That is the only time we use cookie. After that React parses token from the cookie, deletes the cookie and stores it in the local storage and from now on sends it via header.\n\n```javascript\nrouter.get(\n  keys.facebookCallbackURL,\n  passport.authenticate(\"facebook\", {\n    failureRedirect: \"/\",\n    session: false\n  }),\n  (req, res) =\u003e {\n    const token = tokenFromUser(req.user);\n    res.cookie(\"x-auth-cookie\", token);\n    res.redirect(keys.successRedirectURL);\n  }\n);\n```\n\nWe does the same with other two strategies, Google and Local so we could have unified interface for authentication. Don't forget to pass `email` scope in the `authenticate` call so the user is prompted to give us the email also.\n\n```javascript\nrouter.get(\n  \"/auth/facebook\",\n  passport.authenticate(\"facebook\", {\n    scope: [\"public_profile\", \"email\"]\n  })\n);\n```\n\nWe make use of two auth middlewares, `requireLocalAuth` which is called when user tries to log in with email and password and `requireJwtAuth` which is used for all other API calls in the `/routes/api.js` routes.\n\n## Frontend\n\n### Logging in\n\nFirst we assume that user just log on and has cookie so we parse the token from cookie and call `const response = await axios.get(\"/api/user\", { headers });`. If that succeeds token is valid and we set the token in the local storage and delete the cookie.\n\nThe other scenario is that the user is already authenticated and has the token in the local storage so we again we call `/api/user` route and if it succeeds token is valid and we put the user in the store.\n\n```javascript\nexport const logInUser = () =\u003e async (dispatch, getState) =\u003e {\n  try {\n    // register\n    const cookieJwt = Cookies.get(\"x-auth-cookie\");\n    if (cookieJwt) {\n      const headers = {\n        \"Content-Type\": \"application/json\",\n        \"x-auth-token\": cookieJwt\n      };\n\n      const response = await axios.get(\"/api/user\", { headers });\n      localStorage.setItem(\"token\", cookieJwt);\n      Cookies.remove(\"x-auth-cookie\"); //delete just that cookie\n\n      dispatch({\n        type: LOGIN_USER,\n        payload: response.data.user\n      });\n      return;\n    }\n    // logged in\n    const token = localStorage.getItem(\"token\");\n    if (token) {\n      const headers = {\n        \"Content-Type\": \"application/json\",\n        \"x-auth-token\": token\n      };\n\n      const response = await axios.get(\"/api/user\", { headers });\n      dispatch({\n        type: LOGIN_USER,\n        payload: response.data.user\n      });\n      return;\n    }\n  } catch (err) {\n    localStorage.removeItem(\"token\");\n    Cookies.remove(\"x-auth-cookie\");\n    dispatch({\n      type: SET_ERROR,\n      payload: err.response.data\n    });\n  }\n};\n```\n\nThat action creator is called in the `/client/src/layout/Navbar.js` component in the `componentDidMount()` method.\n\nFor the Local strategy is used Register/Login form which design is used from the `Devias.io` [repo](https://github.com/devias-io/react-material-dashboard).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnemanjam%2Freact-material-passport","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnemanjam%2Freact-material-passport","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnemanjam%2Freact-material-passport/lists"}