├── .env.example ├── .gitignore ├── README.md ├── _config.yml ├── assets └── img │ └── logo.svg ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── components │ ├── App.js │ ├── Home.js │ ├── Loading.js │ ├── LoginTransition.js │ └── shared │ │ ├── NotFound.js │ │ ├── Routes.js │ │ ├── _TemplateComponent.js │ │ └── _TemplateStaticHTML.js ├── css │ ├── style.css │ └── style.styl ├── index.js └── utils │ ├── API.js │ ├── AuthService.js │ ├── JSONDebugger.js │ └── init.js └── static.json /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_EXAMPLE=example 2 | REACT_APP_SOMETHING_ELSE=1 3 | 4 | REACT_APP_API_URL=example 5 | REACT_APP_AUTH0_CLIENT_ID={CLIENT_ID} 6 | REACT_APP_AUTH0_DOMAIN={DOMAIN} -------------------------------------------------------------------------------- /.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 | 17 | .vscode 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Demo 2 | ==== 3 | 4 | Live site : [http://react-auth0-api.herokuapp.com/](http://react-auth0-api.herokuapp.com/) 5 | 6 | ![API Demo.gif](https://github.com/iankhor/files/blob/master/ror-auth0-api/2.%20API%20Demo.gif) 7 | 8 | This repository is the React front-end framework that interacts with a Rails API server for my step by step publication at [https://goo.gl/ffaWwr](https://goo.gl/ffaWwr) 9 | 10 | A companion Rails API server can be found at 11 | [https://github.com/iankhor/ror-auth0-api](https://github.com/iankhor/ror-auth0-api) 12 | 13 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-auth0-api", 3 | "license": "MIT", 4 | "version": "0.1.0", 5 | "devDependencies": { 6 | "autoprefixer-stylus": "^0.13.0", 7 | "concurrently": "^3.1.0", 8 | "react-scripts": "0.8.5", 9 | "stylus": "^0.54.5" 10 | }, 11 | "dependencies": { 12 | "auth0-lock": "^10.13.0", 13 | "axios": "^0.15.3", 14 | "bootstrap": "^4.0.0-alpha.6", 15 | "history": "^4.5.1", 16 | "react": "^15.4.2", 17 | "react-addons-css-transition-group": "^15.4.2", 18 | "react-addons-transition-group": "^15.4.2", 19 | "react-dom": "^15.4.2", 20 | "react-router": "4.0.0-beta.7", 21 | "react-router-dom": "v4.0.0-beta.7", 22 | "reactstrap": "^4.3.0" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/iankhor/react-auth0-api.git" 27 | }, 28 | "scripts": { 29 | "start": "PORT=9000 react-scripts start", 30 | "watch": "concurrently --names \"webpack, stylus\" --prefix name \"npm run start\" \"npm run styles:watch\"", 31 | "styles": "stylus -u autoprefixer-stylus ./src/css/style.styl -o ./src/css/style.css", 32 | "styles:watch": "stylus -u autoprefixer-stylus -w ./src/css/style.styl -o ./src/css/style.css", 33 | "build": "react-scripts build", 34 | "test": "react-scripts test --env=jsdom", 35 | "eject": "react-scripts eject" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iankhor/react-auth0-api/0ad6cc0413c5d9391e2be5fb3a6ba2e7e3df420a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | React + Auth0 + Rails API 15 | 16 | 17 |
18 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import logo from '../../assets/img/logo.svg' 3 | import '../css/style.css' 4 | import Home from './Home' 5 | import { auth } from './../utils/init' 6 | 7 | class App extends Component { 8 | constructor(props){ 9 | super(props) 10 | 11 | this.state = { 12 | isLoggedIn: auth.loggedIn(), 13 | } 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 |
20 | logo 21 |

Welcome to React + Auth0 + Rails API

22 |
23 | 24 | 29 | 30 |
31 | ) 32 | } 33 | } 34 | 35 | export default App 36 | -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import JSONDebugger from '../utils/JSONDebugger' 3 | import { pingApiServer, 4 | fetchProfilesWithAuth, 5 | fetchProfilesNoAuth } from './../utils/API' 6 | import { Container, 7 | Row, 8 | Col, 9 | Button, 10 | ButtonGroup } from 'reactstrap' 11 | 12 | import Loading from './Loading' 13 | 14 | class Home extends Component { 15 | constructor(props){ 16 | super(props) 17 | 18 | this.state = { status: "Idle", 19 | pingData: "No data from server yet", 20 | profileData: "No data from server yet" 21 | } 22 | } 23 | 24 | resetStates = () => { 25 | this.setState({ 26 | status: "Idle", 27 | pingData: "No data from server yet", 28 | profileData: "No data from server yet" 29 | }) 30 | } 31 | 32 | renderLoading = () => { 33 | switch(this.state.status) { 34 | case "Fetching": 35 | return 36 | default: 37 | return null 38 | } 39 | } 40 | 41 | pingApi = () => { 42 | this.setState( { status: "Fetching" }) 43 | pingApiServer() 44 | .then( data => { 45 | this.setState({ status: "Fetch completed", pingData: data} ) 46 | } ) 47 | } 48 | 49 | _fetchProfilesNoAuth = () => { 50 | this.setState( { status: "Fetching" }) 51 | fetchProfilesNoAuth() 52 | .then( data => { 53 | this.setState({ status: "Fetch completed", profileData: data} ) 54 | }) 55 | } 56 | 57 | _fetchProfilesWithAuth = () => { 58 | this.setState( { status: "Fetching" }) 59 | fetchProfilesWithAuth(this.props.token) 60 | .then( data => { 61 | this.setState({ status: "Fetch completed", profileData: data} ) 62 | }) 63 | } 64 | 65 | renderSignInUp = () => { 66 | return 67 | } 68 | 69 | renderLogOut = () => { 70 | return ( 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 | ) 93 | } 94 | 95 | render(){ 96 | return( 97 | 98 |
99 | 100 | { this.props.isLoggedIn ? this.renderLogOut() : this.renderSignInUp() } 101 | 102 |
103 | { this.renderLoading() } 104 | { } 105 |
106 | ) 107 | } 108 | } 109 | 110 | export default Home 111 | 112 | -------------------------------------------------------------------------------- /src/components/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Loading = (props) => { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default Loading -------------------------------------------------------------------------------- /src/components/LoginTransition.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Container, Row, Col } from 'reactstrap' 3 | import Loading from './Loading' 4 | 5 | class LoginTransition extends Component { 6 | render(){ 7 | return( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | } 19 | 20 | export default LoginTransition 21 | 22 | -------------------------------------------------------------------------------- /src/components/shared/NotFound.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class NotFound extends Component { 4 | render(){ 5 | return( 6 |
7 |

Not Found page

8 |
9 | ) 10 | } 11 | } 12 | 13 | export default NotFound 14 | 15 | -------------------------------------------------------------------------------- /src/components/shared/Routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | //Routes 4 | import NotFound from './NotFound' 5 | import App from './../App'; 6 | import LoginTransition from './../LoginTransition'; 7 | 8 | import { BrowserRouter, Route, Switch } from 'react-router-dom' 9 | 10 | const Routes = (props) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default Routes -------------------------------------------------------------------------------- /src/components/shared/_TemplateComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import StaticHTMLTemplate from './_TemplateStaticHTML' 3 | 4 | class TemplateComponent extends Component { 5 | render(){ 6 | return( 7 |
8 |

This is a componentTemplate page

9 |

...

10 | 11 |
12 | ) 13 | } 14 | } 15 | 16 | export default TemplateComponent 17 | 18 | -------------------------------------------------------------------------------- /src/components/shared/_TemplateStaticHTML.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const StaticHTMLTemplate = (props) => { 4 | return ( 5 |
6 |

This is from StaticHTMLTemplate

7 |
8 | ) 9 | } 10 | 11 | export default StaticHTMLTemplate -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 2em; 3 | } 4 | .color-silver { 5 | background-color: #ddd; 6 | } 7 | .color-font-aqua { 8 | color: #7fdbff; 9 | } 10 | .color-aqua { 11 | background-color: #7fdbff; 12 | } 13 | .generic-center { 14 | margin: auto; 15 | padding: 2rem; 16 | border: 2px solid #000; 17 | text-align: center; 18 | } 19 | .dummy-height { 20 | min-height: 500px; 21 | } 22 | .border { 23 | border: 2px solid #000; 24 | } 25 | .buttons { 26 | border: 2px solid #000; 27 | padding: 20px; 28 | } 29 | .borderless { 30 | border: none; 31 | } 32 | .App { 33 | text-align: center; 34 | } 35 | .App-logo { 36 | -webkit-animation: App-logo-spin infinite 20s linear; 37 | animation: App-logo-spin infinite 20s linear; 38 | height: 80px; 39 | } 40 | .App-header { 41 | height: 150px; 42 | padding: 20px; 43 | } 44 | .App-intro { 45 | font-size: large; 46 | } 47 | .login-transition { 48 | text-align: center; 49 | padding-top: 50vh; 50 | } 51 | .debugger { 52 | text-align: left; 53 | } 54 | @-webkit-keyframes App-logo-spin { 55 | from { 56 | -webkit-transform: rotate(0deg); 57 | transform: rotate(0deg); 58 | } 59 | to { 60 | -webkit-transform: rotate(360deg); 61 | transform: rotate(360deg); 62 | } 63 | } 64 | @keyframes App-logo-spin { 65 | from { 66 | -webkit-transform: rotate(0deg); 67 | transform: rotate(0deg); 68 | } 69 | to { 70 | -webkit-transform: rotate(360deg); 71 | transform: rotate(360deg); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/css/style.styl: -------------------------------------------------------------------------------- 1 | h1 2 | font-size 2em 3 | 4 | .color-silver 5 | background-color #DDDDDD 6 | 7 | .color-font-aqua 8 | color #7FDBFF 9 | 10 | .color-aqua 11 | background-color #7FDBFF 12 | 13 | .generic-center 14 | margin auto 15 | padding 2rem 16 | border 2px solid #000 17 | text-align center 18 | 19 | .dummy-height 20 | min-height 500px 21 | 22 | .border 23 | border 2px solid #000 24 | 25 | .buttons 26 | border 2px solid #000 27 | padding 20px 28 | 29 | 30 | .borderless 31 | border none 32 | 33 | // App.css 34 | .App 35 | text-align center 36 | 37 | 38 | .App-logo 39 | animation App-logo-spin infinite 20s linear 40 | height 80px 41 | 42 | 43 | .App-header 44 | height 150px 45 | padding 20px 46 | 47 | .App-intro 48 | font-size large 49 | 50 | .login-transition 51 | text-align center 52 | padding-top 50vh 53 | 54 | .debugger 55 | text-align left 56 | 57 | @keyframes App-logo-spin 58 | from { transform: rotate(0deg) } 59 | to { transform: rotate(360deg) } 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import 'bootstrap/dist/css/bootstrap.css' 4 | 5 | // Routes 6 | import Routes from './components/shared/Routes' 7 | 8 | ReactDOM.render( 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /src/utils/API.js: -------------------------------------------------------------------------------- 1 | import { api } from './init' 2 | 3 | export function pingApiServer(){ 4 | return api.get('/') 5 | .then(function (response) { 6 | return response.data 7 | }) 8 | .catch(function (error) { 9 | return error.response.data 10 | }) 11 | } 12 | 13 | export function fetchProfilesNoAuth(){ 14 | return api.get('/profiles') 15 | .then(function (response) { 16 | return response.data 17 | }) 18 | .catch(function (error) { 19 | return error.response.data 20 | }) 21 | } 22 | 23 | export function fetchProfilesWithAuth(token){ 24 | return api.get('/profiles', { headers: { 'Authorization': 'Bearer ' + token } }) 25 | .then(function (response) { 26 | return response.data 27 | }) 28 | .catch(function (error) { 29 | return error.response.data 30 | }) 31 | } -------------------------------------------------------------------------------- /src/utils/AuthService.js: -------------------------------------------------------------------------------- 1 | import Auth0Lock from 'auth0-lock' 2 | import logo from './../../assets/img/logo.svg' 3 | 4 | export default class AuthService { 5 | constructor(clientId, domain) { 6 | // Configure Auth0 7 | this.lock = new Auth0Lock(clientId, domain, { 8 | auth: { 9 | redirectUrl: `${window.location.origin}/auth`, 10 | responseType: 'token' 11 | }, 12 | theme: { 13 | logo: logo, 14 | primaryColor: '#7FDBFF' 15 | }, 16 | languageDictionary: { 17 | title: "React + Auth0 + Rails API" 18 | } 19 | }) 20 | // Add callback for lock `authenticated` event 21 | this.lock.on('authenticated', this._doAuthentication.bind(this)) 22 | // binds login functions to keep this context 23 | this.login = this.login.bind(this) 24 | } 25 | 26 | _doAuthentication(authResult) { 27 | // Saves the user token 28 | this.setToken(authResult.idToken) 29 | // navigate to the home route 30 | 31 | // temp hack 32 | location.replace("/"); 33 | 34 | } 35 | 36 | login() { 37 | // Call the show method to display the widget. 38 | this.lock.show() 39 | } 40 | 41 | loggedIn() { 42 | // Checks if there is a saved token and it's still valid 43 | return !!this.getToken() 44 | } 45 | 46 | setToken(idToken) { 47 | // Saves user token to local storage 48 | localStorage.setItem('id_token', idToken) 49 | } 50 | 51 | getToken() { 52 | // Retrieves the user token from local storage 53 | return localStorage.getItem('id_token') 54 | } 55 | 56 | logout() { 57 | // Clear user token and profile data from local storage 58 | localStorage.removeItem('id_token'); 59 | 60 | // temp hack 61 | location.replace("/"); 62 | } 63 | } -------------------------------------------------------------------------------- /src/utils/JSONDebugger.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class JSONDebugger extends Component { 4 | render(){ 5 | return( 6 |
 7 |                 

JSON Debugger

8 | {JSON.stringify(this.props.json, null, 2)} 9 |
10 | ) 11 | } 12 | } 13 | 14 | export default JSONDebugger 15 | 16 | -------------------------------------------------------------------------------- /src/utils/init.js: -------------------------------------------------------------------------------- 1 | import AuthService from './AuthService' 2 | import axios from 'axios' 3 | 4 | export const auth = new AuthService( 5 | process.env.REACT_APP_AUTH0_CLIENT_ID, 6 | process.env.REACT_APP_AUTH0_DOMAIN) 7 | 8 | export const api = axios.create({ 9 | baseURL: process.env.REACT_APP_API_URL 10 | }) 11 | -------------------------------------------------------------------------------- /static.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "build/", 3 | "clean_urls": false, 4 | "routes": { 5 | "/**": "index.html" 6 | }, 7 | "headers": { 8 | "/**": { 9 | "Strict-Transport-Security": "max-age=7776000" 10 | } 11 | } 12 | } --------------------------------------------------------------------------------