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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------