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

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 |
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 |
94 |
Welcome, {this.state.username}
95 | Begin chatting here.
96 |
97 |
98 |
99 |
100 | )
101 | }
102 | }
103 |
104 | export default Chat;
105 |
--------------------------------------------------------------------------------