├── client ├── src │ ├── index.css │ ├── App.test.js │ ├── App.css │ ├── index.js │ ├── App.js │ ├── login.css │ ├── components │ │ ├── withAuth.js │ │ └── AuthHelperMethods.js │ ├── signup.js │ ├── logo.svg │ ├── login.js │ └── registerServiceWorker.js ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── .gitignore └── package.json ├── public └── stylesheets │ └── style.css ├── README.md ├── config └── config.json ├── models ├── User.js └── index.js ├── package.json ├── .gitignore ├── app.js └── bin └── www /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchvalbo/jwt_react_node_starting_template/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jwt_react_node_starting_template 2 | This is a starting template for a Medium Article. 3 | 4 | https://medium.com/@romanchvalbo/how-i-set-up-react-and-node-with-json-web-token-for-authentication-259ec1a90352 5 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://localhost:3001", 6 | "dependencies": { 7 | "axios": "^0.18.0", 8 | "jwt-decode": "^2.2.0", 9 | "react": "^16.4.1", 10 | "react-dom": "^16.4.1", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "username": "root", 4 | "password": "RJFtco2007", 5 | "database": "authentication", 6 | "host": "127.0.0.1", 7 | "dialect": "mysql" 8 | }, 9 | "test": { 10 | "username": "root", 11 | "password": null, 12 | "database": "database_test", 13 | "host": "127.0.0.1", 14 | "dialect": "mysql" 15 | }, 16 | "production": { 17 | "username": "root", 18 | "password": null, 19 | "database": "database_production", 20 | "host": "127.0.0.1", 21 | "dialect": "mysql" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | module.exports = function(sequelize, DataTypes) { 2 | var user = sequelize.define('user', { 3 | id: { 4 | type: DataTypes.INTEGER, 5 | autoIncrement: true, 6 | primaryKey: true, 7 | field: 'id' 8 | }, 9 | username: { 10 | type: DataTypes.STRING, 11 | allowNull: true 12 | }, 13 | password: { 14 | type: DataTypes.STRING, 15 | allowNull: true 16 | } 17 | }, { 18 | freezeTableName: true 19 | }); 20 | 21 | return user; 22 | } -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .main-page { 6 | display: flex; 7 | flex: 1; 8 | flex-direction: column; 9 | height: 100vh; 10 | } 11 | 12 | .top-section { 13 | display: flex; 14 | flex: 1; 15 | justify-content: center; 16 | align-items: center; 17 | background: rgb(236, 236, 236); 18 | } 19 | 20 | .bottom-section { 21 | display: flex; 22 | flex: 3; 23 | justify-content: center; 24 | align-items: center; 25 | background: rgb(255, 255, 255); 26 | } 27 | 28 | .bottom-section button { 29 | height: 20%; 30 | width: 20%; 31 | font-size: 30px; 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-demo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "author": "Roman Chvalbo", 6 | "scripts": { 7 | "server": "node app.js", 8 | "client": "cd client && npm start", 9 | "startboth": "concurrently \"nodemon app.js\" \"npm run client\"", 10 | "start": "node app.js" 11 | }, 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "body-parser": "~1.18.2", 15 | "concurrently": "^3.5.1", 16 | "cookie-parser": "~1.4.3", 17 | "express": "~4.15.5", 18 | "express-jwt": "^5.3.1", 19 | "express-session": "^1.15.6", 20 | "jsonwebtoken": "^8.2.2", 21 | "mysql2": "^1.5.3", 22 | "nodemon": "^1.17.3", 23 | "react-router-dom": "^4.3.1", 24 | "sequelize": "^4.38.0", 25 | "sequelize-cli": "^4.0.0", 26 | "uuid": "^3.2.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter as Router, Route } from 'react-router-dom'; 4 | import './index.css'; 5 | import App from './App'; 6 | import Login from './login'; 7 | import Signup from './signup'; 8 | import registerServiceWorker from './registerServiceWorker'; 9 | 10 | /* Here we will create our routes right off the bat in order to 11 | prevent the user from getting very far in our app without authentication. */ 12 | ReactDOM.render( 13 | 14 |
15 | 16 | 17 | 18 |
19 |
, 20 | document.getElementById('root')); 21 | registerServiceWorker(); 22 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var basename = path.basename(__filename); 7 | var env = process.env.NODE_ENV || 'development'; 8 | var config = require(__dirname + '/../config/config.json')[env]; 9 | var db = {}; 10 | 11 | if (config.use_env_variable) { 12 | var sequelize = new Sequelize(process.env[config.use_env_variable], config); 13 | } else { 14 | var sequelize = new Sequelize(config.database, config.username, config.password, config); 15 | } 16 | 17 | fs 18 | .readdirSync(__dirname) 19 | .filter(file => { 20 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 21 | }) 22 | .forEach(file => { 23 | var model = sequelize['import'](path.join(__dirname, file)); 24 | db[model.name] = model; 25 | }); 26 | 27 | Object.keys(db).forEach(modelName => { 28 | if (db[modelName].associate) { 29 | db[modelName].associate(db); 30 | } 31 | }); 32 | 33 | db.sequelize = sequelize; 34 | db.Sequelize = Sequelize; 35 | 36 | module.exports = db; 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | 4 | 5 | class App extends Component { 6 | 7 | /* Create a new instance of the 'AuthHelperMethods' compoenent*/ 8 | state = { 9 | username: "", 10 | password: "" 11 | } 12 | 13 | /* Here will want to add a method to log the user out upon clicking 'Logout' */ 14 | _handleLogout = () => { 15 | 16 | 17 | this.props.history.replace('/login'); 18 | } 19 | 20 | //Render the protected component 21 | render() { 22 | let name = null; 23 | 24 | //This will be null until we set up authentication... 25 | if (this.props.confirm) { 26 | name = this.props.confirm.username; 27 | } 28 | 29 | return ( 30 |
31 |
32 |
33 |

Welcome, {name}

34 |
35 |
36 | 37 |
38 |
39 |
40 | ); 41 | } 42 | } 43 | 44 | //In order for this component to be protected, we must wrap it with what we call a 'Higher Order Component' or HOC. 45 | export default App; 46 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var cookieParser = require('cookie-parser'); 4 | var bodyParser = require('body-parser'); 5 | 6 | var bcrypt = require('bcryptjs'); 7 | const jwt = require('jsonwebtoken'); 8 | const exjwt = require('express-jwt'); 9 | 10 | const PORT = process.env.PORT || 3001; 11 | var app = express(); 12 | 13 | // Requiring our models for syncing 14 | var db = require("./models"); 15 | 16 | /*========= Here we want to let the server know that we should expect and allow a header with the content-type of 'Authorization' ============*/ 17 | app.use((req, res, next) => { 18 | res.setHeader('Access-Control-Allow-Headers', 'Content-type,Authorization'); 19 | next(); 20 | }); 21 | 22 | /*========= This is the typical node server setup so we can be able to parse the requests/responses coming in and out of the server ============*/ 23 | app.use(bodyParser.urlencoded({ extended: true })); 24 | app.use(bodyParser.json()); 25 | app.use(cookieParser()); 26 | 27 | /*========= Here we will set up an express jsonwebtoken middleware(simply required for express to properly utilize the token for requests) You MUST instantiate this with the same secret that will be sent to the client ============*/ 28 | 29 | 30 | /* This is SUPER important! This is the route that the client will be passing the entered credentials for verification to. If the credentials match, then the server sends back a json response with a valid json web token for the client to use for identification. */ 31 | 32 | db.sequelize.sync().then(() => { 33 | app.listen(PORT, function () { 34 | console.log("App listening on PORT " + PORT); 35 | }); 36 | }) 37 | 38 | 39 | module.exports = app; 40 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('jwt-demo:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /client/src/login.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Raleway'); 2 | 3 | .main-wrapper { 4 | display: flex; 5 | background: rgb(91, 91, 209); 6 | height: 100vh; 7 | width: 100vw; 8 | justify-content: center; 9 | align-items: center; 10 | } 11 | 12 | .signiture { 13 | position: absolute; 14 | bottom: 0; 15 | left: 10px; 16 | color: rgb(177, 177, 241); 17 | } 18 | 19 | .box { 20 | display: flex; 21 | padding: 0px 40px; 22 | padding-top: 20px; 23 | border-radius: 20px; 24 | width: 40%; 25 | flex-direction: column; 26 | justify-content: space-around; 27 | align-items: center; 28 | background: linear-gradient(to bottom right, rgb(255, 255, 255), rgb(195, 195, 195)); 29 | box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.63); 30 | } 31 | 32 | .box-header { 33 | display: flex; 34 | font-size: 30px; 35 | margin-bottom: 40px; 36 | } 37 | 38 | .box-header > h1 { 39 | margin: 0; 40 | font-weight: 500; 41 | } 42 | 43 | .box-form { 44 | display: flex; 45 | flex-direction: column; 46 | justify-content: center; 47 | align-items: center; 48 | width: 100%; 49 | } 50 | 51 | .box-form input { 52 | border-style: solid; 53 | border-width: 1px; 54 | border-radius: 5px; 55 | text-align: center; 56 | font-size: 20px; 57 | border-color: rgb(202, 202, 202); 58 | width: 100%; 59 | padding: 10px 0px; 60 | margin-bottom: 10px; 61 | } 62 | 63 | 64 | .box-form input:last-child { 65 | width: 100%; 66 | padding: 20px 0px; 67 | } 68 | 69 | .box-form button { 70 | padding: 20px 0px; 71 | width: 40%; 72 | margin: 0; 73 | margin-top: 20px; 74 | font-size: 20px; 75 | font-weight: 500; 76 | cursor: pointer; 77 | } 78 | 79 | .link { 80 | text-align: center; 81 | text-decoration: none; 82 | font-size: 20px; 83 | margin-top: 30px; 84 | margin-bottom: 5px; 85 | width:100%; 86 | } 87 | 88 | .link-signup { 89 | font-weight: 500; 90 | color: rgb(91, 91, 209); 91 | } 92 | -------------------------------------------------------------------------------- /client/src/components/withAuth.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import AuthHelperMethods from './AuthHelperMethods'; 3 | 4 | /* A higher order component is frequently written as a function that returns a class. */ 5 | export default function withAuth(AuthComponent) { 6 | 7 | const Auth = new AuthHelperMethods(); 8 | 9 | return class AuthWrapped extends Component { 10 | state = { 11 | confirm: null, 12 | loaded: false 13 | } 14 | 15 | /* In the componentDidMount, we would want to do a couple of important tasks in order to verify the current users authentication status 16 | prior to granting them enterance into the app. */ 17 | componentWillMount() { 18 | if (!Auth.loggedIn()) { 19 | this.props.history.replace('/login') 20 | } 21 | else { 22 | /* Try to get confirmation message from the Auth helper. */ 23 | try { 24 | 25 | const confirm = Auth.getConfirm() 26 | console.log("confirmation is:", confirm); 27 | this.setState({ 28 | confirm: confirm, 29 | loaded: true 30 | }) 31 | } 32 | /* Oh snap! Looks like there's an error so we'll print it out and log the user out for security reasons. */ 33 | catch (err) { 34 | console.log(err); 35 | Auth.logout() 36 | this.props.history.replace('/login'); 37 | } 38 | } 39 | } 40 | 41 | render() { 42 | if (this.state.loaded == true) { 43 | if (this.state.confirm) { 44 | console.log("confirmed!") 45 | return ( 46 | /* component that is currently being wrapped(App.js) */ 47 | 48 | ) 49 | } 50 | else { 51 | console.log("not confirmed!") 52 | return null 53 | } 54 | } 55 | else { 56 | return null 57 | } 58 | 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /client/src/signup.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | 3 | import './login.css' 4 | import axios from "axios"; 5 | import { Link } from 'react-router-dom'; 6 | 7 | export default class Signup extends Component { 8 | 9 | 10 | state = { 11 | username: "", 12 | password: "" 13 | } 14 | 15 | _handleChange = (e) => { 16 | 17 | this.setState( 18 | { 19 | [e.target.name]: e.target.value 20 | } 21 | ) 22 | 23 | console.log(this.state); 24 | } 25 | 26 | handleFormSubmit = (e) => { 27 | 28 | e.preventDefault(); 29 | 30 | 31 | 32 | 33 | } 34 | 35 | render() { 36 | return ( 37 | 38 |
39 |
40 |
41 |

Signup

42 |
43 |
44 | 51 | 58 | 59 |
60 | Already have an account? Login 61 |
62 |
63 |

Template Built & Designed by Roman Chvalbo

64 |
65 |
66 | 67 |
68 | ); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /client/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | /* We want to import our 'AuthHelperMethods' component in order to send a login request */ 4 | 5 | import { Link } from 'react-router-dom'; 6 | import './login.css' 7 | 8 | 9 | class Login extends Component { 10 | 11 | /* In order to utilize our authentication methods within the AuthService class, we want to instantiate a new object */ 12 | 13 | state = { 14 | username: "", 15 | password: "" 16 | } 17 | 18 | /* Fired off every time the use enters something into the input fields */ 19 | _handleChange = (e) => { 20 | this.setState( 21 | { 22 | [e.target.name]: e.target.value 23 | } 24 | ) 25 | } 26 | 27 | handleFormSubmit = (e) => { 28 | e.preventDefault(); 29 | 30 | /* Here is where all the login logic will go. Upon clicking the login button, we would like to utilize a login method that will send our entered credentials over to the server for verification. Once verified, it should store your token and send you to the protected route. */ 31 | } 32 | 33 | componentWillMount() { 34 | 35 | /* Here is a great place to redirect someone who is already logged in to the protected route */ 36 | 37 | } 38 | 39 | render() { 40 | return ( 41 | 42 |
43 |
44 |
45 |

Login

46 |
47 |
48 | 55 | 62 | 63 |
64 | Don't have an account? Signup 65 |
66 |
67 |

Template Built & Designed by Roman Chvalbo

68 |
69 |
70 | 71 |
72 | ); 73 | } 74 | 75 | } 76 | 77 | export default Login; -------------------------------------------------------------------------------- /client/src/components/AuthHelperMethods.js: -------------------------------------------------------------------------------- 1 | import decode from 'jwt-decode'; 2 | 3 | export default class AuthHelperMethods { 4 | // Initializing important variables 5 | login = (username, password) => { 6 | 7 | // Get a token from api server using the fetch api 8 | return this.fetch(`/log-in`, { 9 | method: 'POST', 10 | body: JSON.stringify({ 11 | username, 12 | password 13 | }) 14 | }).then(res => { 15 | 16 | this.setToken(res.token) // Setting the token in localStorage 17 | return Promise.resolve(res); 18 | }) 19 | } 20 | 21 | loggedIn = () => { 22 | // Checks if there is a saved token and it's still valid 23 | const token = this.getToken() // Getting token from localstorage 24 | 25 | //The double exclamation is a way to cast the variable to a boolean, allowing you to easily check if the token exusts. 26 | return !!token && !this.isTokenExpired(token) // handwaiving here 27 | } 28 | 29 | isTokenExpired = (token) => { 30 | try { 31 | const decoded = decode(token); 32 | if (decoded.exp < Date.now() / 1000) { // Checking if token is expired. 33 | return true; 34 | } 35 | else 36 | return false; 37 | } 38 | catch (err) { 39 | console.log("expired check failed! Line 42: AuthService.js"); 40 | return false; 41 | } 42 | } 43 | 44 | setToken = (idToken) => { 45 | // Saves user token to localStorage 46 | localStorage.setItem('id_token', idToken) 47 | } 48 | 49 | getToken = () => { 50 | // Retrieves the user token from localStorage 51 | return localStorage.getItem('id_token') 52 | } 53 | 54 | logout = () => { 55 | // Clear user token and profile data from localStorage 56 | localStorage.removeItem('id_token'); 57 | } 58 | 59 | getConfirm = () => { 60 | // Using jwt-decode npm package to decode the token 61 | let answer = decode(this.getToken()); 62 | console.log("Recieved answer!"); 63 | return answer; 64 | } 65 | 66 | fetch = (url, options) => { 67 | // performs api calls sending the required authentication headers 68 | const headers = { 69 | 'Accept': 'application/json', 70 | 'Content-Type': 'application/json' 71 | } 72 | // Setting Authorization header 73 | // Authorization: Bearer xxxxxxx.xxxxxxxx.xxxxxx 74 | if (this.loggedIn()) { 75 | headers['Authorization'] = 'Bearer ' + this.getToken() 76 | } 77 | 78 | return fetch(url, { 79 | headers, 80 | ...options 81 | }) 82 | .then(this._checkStatus) 83 | .then(response => response.json()) 84 | } 85 | 86 | _checkStatus = (response) => { 87 | // raises an error in case response status is not a success 88 | if (response.status >= 200 && response.status < 300) { // Success status lies between 200 to 300 89 | return response 90 | } else { 91 | var error = new Error(response.statusText) 92 | error.response = response 93 | throw error 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /client/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | --------------------------------------------------------------------------------