├── public ├── favicon.ico └── index.html ├── src ├── index.css ├── App.css ├── history.js ├── Profile │ ├── Profile.css │ └── Profile.js ├── Auth │ ├── auth0-variables.js │ └── Auth.js ├── App.test.js ├── index.js ├── Chat │ ├── Chat.css │ └── Chat.js ├── Callback │ ├── Callback.js │ └── loading.svg ├── Home │ └── Home.js ├── routes.js └── App.js ├── .idea └── vcs.xml ├── .gitignore ├── README.md ├── package.json └── server.js /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yomete/pusher-auth0/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .btn-margin { 2 | margin: 7px 3px; 3 | } 4 | .no-border { 5 | border-radius: 0px; 6 | } 7 | -------------------------------------------------------------------------------- /src/history.js: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/createBrowserHistory' 2 | 3 | export default createHistory({ 4 | forceRefresh: true 5 | }) -------------------------------------------------------------------------------- /src/Profile/Profile.css: -------------------------------------------------------------------------------- 1 | .profile-area img { 2 | max-width: 150px; 3 | margin-bottom: 20px; 4 | } 5 | 6 | .panel-body h3 { 7 | margin-top: 0; 8 | } -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Auth/auth0-variables.js: -------------------------------------------------------------------------------- 1 | export const AUTH_CONFIG = { 2 | domain: 'yourname.auth0.com', 3 | clientId: 'xxxxxxxxxxxxxx', 4 | callbackUrl: 'http://localhost:3000/callback' //Callback URL set in the Auth0 dasbhoard. 5 | } 6 | -------------------------------------------------------------------------------- /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 | }); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://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 15 | npm-debug.log 16 | .idea 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import './index.css'; 3 | import 'bootstrap/dist/css/bootstrap.css'; 4 | import { makeMainRoutes } from './routes'; 5 | 6 | const routes = makeMainRoutes(); 7 | 8 | ReactDOM.render( 9 | routes, 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /src/Chat/Chat.css: -------------------------------------------------------------------------------- 1 | .chat-container { 2 | margin-top: 50px; 3 | } 4 | .chatmessage-container { 5 | border: 1px solid #ccc; 6 | border-radius: 5px; 7 | margin: 20px auto; 8 | width: 700px; 9 | display: table; 10 | } 11 | .message-box { 12 | background-color: #eee; 13 | padding: 20px; 14 | border-bottom: 1px solid #cccccc; 15 | } -------------------------------------------------------------------------------- /src/Callback/Callback.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import loading from './loading.svg'; 3 | 4 | class Callback extends Component { 5 | render() { 6 | const style = { 7 | position: 'absolute', 8 | display: 'flex', 9 | justifyContent: 'center', 10 | height: '100vh', 11 | width: '100vw', 12 | top: 0, 13 | bottom: 0, 14 | left: 0, 15 | right: 0, 16 | backgroundColor: 'white', 17 | } 18 | 19 | return ( 20 |
21 | loading 22 |
23 | ); 24 | } 25 | } 26 | 27 | export default Callback; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactChat 2 | 3 | This app demonstrates how to use ReactJS, Auth0, and Pusher to create a chat app. 4 | 5 | You can read the tutorial here on Auth0 - https://auth0.com/blog/build-a-chat-app-with-react/ 6 | 7 | ## How to run this app 8 | 9 | 1. Clone the repo - `https://github.com/yomete/pusher-auth0` 10 | 2. Open `src/Auth/auth0-variables.js`, `server.js` and `src/Chat/Chat.js`and edit with your Auth0 and Pusher credentials which can be gotten from the [Pusher dashboard](https://pusher.com) 11 | 3. In the root of the project folder, run `yarn` to install all dependencies. 12 | 4. Then run `yarn start` to get the app up and running. 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pusher-auth0", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.9.5" 7 | }, 8 | "dependencies": { 9 | "auth0-js": "^8.7.0", 10 | "axios": "^0.16.2", 11 | "body-parser": "^1.17.2", 12 | "bootstrap": "^3.3.7", 13 | "events": "^1.1.1", 14 | "express": "^4.15.3", 15 | "history": "^4.6.1", 16 | "nodemon": "^1.11.0", 17 | "pusher": "^1.5.1", 18 | "pusher-js": "^4.1.0", 19 | "react": "^15.5.4", 20 | "react-bootstrap": "^0.31.0", 21 | "react-dom": "^15.5.4", 22 | "react-router": "^4.1.1", 23 | "react-router-dom": "^4.1.1" 24 | }, 25 | "scripts": { 26 | "start": "nodemon server.js & react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test --env=jsdom", 29 | "eject": "react-scripts eject" 30 | }, 31 | "proxy": "http://localhost:5000" 32 | } 33 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const bodyParser = require("body-parser"); 4 | const app = express(); 5 | const Pusher = require('pusher'); 6 | 7 | const pusher = new Pusher({ 8 | appId: 'APP_ID', 9 | key: 'APP_KEY', 10 | secret: 'SECRET', 11 | cluster: 'YOUR CLUSTER', 12 | encrypted: true 13 | }); 14 | 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({ extended: false })); 17 | 18 | app.post('/message/send', (req, res) => { 19 | pusher.trigger( 'private-reactchat', 'messages', { 20 | message: req.body.message, 21 | username: req.body.username 22 | }); 23 | res.sendStatus(200); 24 | }); 25 | 26 | app.post('/pusher/auth', (req, res) => { 27 | console.log('POST to /pusher/auth'); 28 | const socketId = req.body.socket_id; 29 | const channel = req.body.channel_name; 30 | const auth = pusher.authenticate(socketId, channel); 31 | res.send(auth); 32 | }); 33 | 34 | app.set('port', (process.env.PORT || 5000)); 35 | 36 | app.listen(app.get('port'), function() { 37 | console.log('Node app is running on port', app.get('port')); 38 | }); 39 | -------------------------------------------------------------------------------- /src/Profile/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Panel, ControlLabel, Glyphicon } from 'react-bootstrap'; 3 | import './Profile.css'; 4 | 5 | class Profile extends Component { 6 | componentWillMount() { 7 | this.setState({ profile: {} }); 8 | const { userProfile, getProfile } = this.props.auth; 9 | 10 | if (!userProfile) { 11 | getProfile((err, profile) => { 12 | this.setState({ profile }); 13 | }); 14 | } else { 15 | this.setState({ profile: userProfile }); 16 | } 17 | } 18 | render() { 19 | const { profile } = this.state; 20 | return ( 21 |
22 |
23 |

{profile.name}

24 | 25 | profile 26 |
27 | Nickname 28 |

{profile.nickname}

29 |
30 |
{JSON.stringify(profile, null, 2)}
31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | 38 | export default Profile; 39 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | ReactChat 17 | 18 | 19 |
20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | class Home extends Component { 5 | componentWillMount() { 6 | const { isAuthenticated, getProfile } = this.props.auth; 7 | 8 | if (isAuthenticated() ) { 9 | getProfile(); 10 | } 11 | } 12 | login() { 13 | this.props.auth.login(); 14 | } 15 | 16 | render() { 17 | const { isAuthenticated } = this.props.auth; 18 | return ( 19 |
20 |
21 |

Welcome to ReactChat!

22 | { 23 | !isAuthenticated() && ( 24 |
25 |

We need you to sign in/sign up with Auth0 before you can access our chat. 😁

26 |

Login

27 |
28 | ) 29 | } 30 | { 31 | isAuthenticated() && ( 32 |
33 |

Let's chat. 😁

34 | Chat 35 |
36 | ) 37 | } 38 |
39 | {this.props.children} 40 |
41 | ); 42 | } 43 | } 44 | 45 | export default Home; 46 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect, Route, BrowserRouter } from 'react-router-dom'; 3 | import App from './App'; 4 | import Home from './Home/Home'; 5 | import Profile from './Profile/Profile'; 6 | import Chat from './Chat/Chat'; 7 | import Callback from './Callback/Callback'; 8 | import Auth from './Auth/Auth'; 9 | import history from './history'; 10 | 11 | const auth = new Auth(); 12 | 13 | const handleAuthentication = (nextState, replace) => { 14 | if (/access_token|id_token|error/.test(nextState.location.hash)) { 15 | auth.handleAuthentication(); 16 | } 17 | } 18 | 19 | export const makeMainRoutes = () => { 20 | return ( 21 | 22 |
23 | } /> 24 | } /> 25 | ( 26 | !auth.isAuthenticated() ? ( 27 | 28 | ) : ( 29 | 30 | ) 31 | )} /> 32 | ( 33 | !auth.isAuthenticated() ? ( 34 | 35 | ) : ( 36 | 37 | ) 38 | )} /> 39 | { 40 | handleAuthentication(props); 41 | return 42 | }}/> 43 |
44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/Callback/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Navbar, Nav, Button } from 'react-bootstrap'; 3 | import './App.css'; 4 | 5 | class App extends Component { 6 | goTo(route) { 7 | this.props.history.replace(`/${route}`) 8 | } 9 | 10 | login() { 11 | this.props.auth.login(); 12 | } 13 | 14 | logout() { 15 | this.props.auth.logout(); 16 | } 17 | 18 | render() { 19 | const { isAuthenticated } = this.props.auth; 20 | 21 | return ( 22 |
23 | 24 | 25 | 26 | ReactChat 27 | 28 | 29 | 77 | 78 |
79 | ); 80 | } 81 | } 82 | 83 | export default App; 84 | -------------------------------------------------------------------------------- /src/Auth/Auth.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import auth0 from 'auth0-js'; 3 | import { AUTH_CONFIG } from './auth0-variables'; 4 | import history from '../history'; 5 | 6 | export default class Auth extends EventEmitter { 7 | auth0 = new auth0.WebAuth({ 8 | domain: AUTH_CONFIG.domain, 9 | clientID: AUTH_CONFIG.clientId, 10 | redirectUri: AUTH_CONFIG.callbackUrl, 11 | audience: `https://${AUTH_CONFIG.domain}/userinfo`, 12 | responseType: 'token id_token', 13 | scope: 'openid profile' 14 | }); 15 | 16 | userProfile; 17 | 18 | constructor() { 19 | super(); 20 | this.login = this.login.bind(this); 21 | this.logout = this.logout.bind(this); 22 | this.handleAuthentication = this.handleAuthentication.bind(this); 23 | this.isAuthenticated = this.isAuthenticated.bind(this); 24 | this.getAccessToken = this.getAccessToken.bind(this); 25 | this.getProfile = this.getProfile.bind(this); 26 | } 27 | 28 | login() { 29 | this.auth0.authorize(); 30 | } 31 | 32 | handleAuthentication() { 33 | this.auth0.parseHash((err, authResult) => { 34 | console.log(err) 35 | console.log(authResult) 36 | if (authResult && authResult.accessToken && authResult.idToken) { 37 | this.setSession(authResult); 38 | history.replace('/home'); 39 | } else if (err) { 40 | history.replace('/home'); 41 | console.log(err); 42 | alert(`Error: ${err.error}. Check the console for further details.`); 43 | } 44 | }); 45 | } 46 | 47 | setSession(authResult) { 48 | if (authResult && authResult.accessToken && authResult.idToken) { 49 | // Set the time that the access token will expire at 50 | let expiresAt = JSON.stringify( 51 | authResult.expiresIn * 1000 + new Date().getTime() 52 | ); 53 | localStorage.setItem('access_token', authResult.accessToken); 54 | localStorage.setItem('id_token', authResult.idToken); 55 | localStorage.setItem('expires_at', expiresAt); 56 | // navigate to the home route 57 | history.replace('/home'); 58 | } 59 | } 60 | 61 | getAccessToken() { 62 | const accessToken = localStorage.getItem('access_token'); 63 | if (!accessToken) { 64 | throw new Error('No access token found'); 65 | } 66 | return accessToken; 67 | } 68 | 69 | getProfile(cb) { 70 | let accessToken = this.getAccessToken(); 71 | this.auth0.client.userInfo(accessToken, (err, profile) => { 72 | if (profile) { 73 | this.userProfile = profile; 74 | localStorage.username = profile.nickname; 75 | } 76 | cb(err, profile); 77 | }); 78 | } 79 | 80 | logout() { 81 | // Clear access token and ID token from local storage 82 | localStorage.removeItem('access_token'); 83 | localStorage.removeItem('id_token'); 84 | localStorage.removeItem('expires_at'); 85 | localStorage.clear() 86 | this.userProfile = null; 87 | // navigate to the home route 88 | history.replace('/home'); 89 | } 90 | 91 | isAuthenticated() { 92 | // Check whether the current time is past the 93 | // access token's expiry time 94 | let expiresAt = JSON.parse(localStorage.getItem('expires_at')); 95 | return new Date().getTime() < expiresAt; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Chat/Chat.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './Chat.css' 3 | import { FormControl, Grid, Row, Col } from 'react-bootstrap'; 4 | import axios from 'axios' 5 | import Pusher from 'pusher-js' 6 | 7 | class Chat extends Component { 8 | constructor() { 9 | super(); 10 | this.state = { 11 | value: '', 12 | username: '', 13 | messages: [], 14 | }; 15 | this.sendMessage = this.sendMessage.bind(this); 16 | this.handleChange = this.handleChange.bind(this); 17 | } 18 | componentWillMount() { 19 | this.setState({ username: localStorage.username }); 20 | this.pusher = new Pusher('APP_KEY', { 21 | authEndpoint: '/pusher/auth', 22 | cluster: 'YOUR CLUSTER', 23 | encrypted: true 24 | }); 25 | this.chatRoom = this.pusher.subscribe('private-reactchat'); 26 | } 27 | componentDidMount() { 28 | this.chatRoom.bind('messages', newmessage => { 29 | this.setState({messages: this.state.messages.concat(newmessage)}) 30 | }, this); 31 | 32 | } 33 | handleChange(event) { 34 | this.setState({value: event.target.value}); 35 | } 36 | sendMessage(event) { 37 | event.preventDefault(); 38 | if (this.state.value !== '') { 39 | axios.post('/message/send', { 40 | username: this.state.username, 41 | message: this.state.value 42 | }) 43 | .then(response => { 44 | console.log(response) 45 | }) 46 | .catch(error => { 47 | console.log(error) 48 | }) 49 | this.setState({value: ''}) 50 | } 51 | else { 52 | console.log('enter message') 53 | } 54 | } 55 | render() { 56 | const messages = this.state.messages; 57 | 58 | const message = messages.map(item => { 59 | return ( 60 | 61 | {message} 62 | 63 | 64 |
65 |
66 |

{item.username}

67 |

{item.message}

68 |
69 |
70 | 71 |
72 |
73 | ) 74 | }) 75 | return ( 76 | 77 | 78 | 79 | {message} 80 |
81 |
82 | 83 | 89 | 90 | 91 | 92 | 93 | 94 |

Welcome, {this.state.username}

95 |
Begin chatting here.
96 |
97 | 98 |
99 |
100 | ) 101 | } 102 | } 103 | 104 | export default Chat; 105 | --------------------------------------------------------------------------------