├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico └── index.html ├── server ├── package.json └── server.js └── src ├── App.css ├── components ├── Callback.js ├── CelebrityJokes.js ├── FoodJokes.js └── Nav.js ├── index.css ├── index.js ├── logo.svg └── utils ├── AuthService.js └── chucknorris-api.js /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ReactJS Authentication Tutorial 2 | 3 | This is the code that accompanies the **ReactJS Authentication Tutorial** on *Auth0 Blog* 4 | 5 | ![World Chuck Norris App](https://cdn.auth0.com/blog/react/login.png) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | 11 | # Get the project 12 | git clone https://github.com/auth0-blog/reactjs-authentication-tutorial.git reactjs-authentication-tutorial 13 | 14 | # Change directory 15 | cd reactjs-authentication-tutorial 16 | 17 | # Install the dependencies 18 | npm install 19 | 20 | # Run your app 21 | npm start 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deals", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.8.5" 7 | }, 8 | "dependencies": { 9 | "auth0-js": "9.0.0-beta.5", 10 | "axios": "^0.15.3", 11 | "jwt-decode": "^2.1.0", 12 | "react": "^16.0.0", 13 | "react-dom": "^16.0.0", 14 | "react-router": "^3.0.0" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-blog/reactjs-authentication-tutorial/f6f8bb29a547e0f1d4b72d643a60ff829234b63c/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | React App 18 | 19 | 20 |
21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chuck-norris-jokes", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js", 9 | "dev": "nodemon server.js" 10 | }, 11 | "author": "Auth0", 12 | "license": "MIT", 13 | "dependencies": { 14 | "body-parser": "^1.15.2", 15 | "cors": "^2.8.1", 16 | "express": "^4.14.0", 17 | "express-jwt": "^3.4.0", 18 | "jwks-rsa": "^1.2.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const app = express(); 5 | const jwt = require('express-jwt'); 6 | const jwks = require('jwks-rsa'); 7 | const cors = require('cors'); 8 | const bodyParser = require('body-parser'); 9 | 10 | app.use(bodyParser.json()); 11 | app.use(bodyParser.urlencoded({ extended: true })); 12 | app.use(cors()); 13 | 14 | const authCheck = jwt({ 15 | secret: jwks.expressJwtSecret({ 16 | cache: true, 17 | rateLimit: true, 18 | jwksRequestsPerMinute: 5, 19 | // YOUR-AUTH0-DOMAIN name e.g https://prosper.auth0.com 20 | jwksUri: "{YOUR-AUTH0-DOMAIN}/.well-known/jwks.json" 21 | }), 22 | // This is the identifier we set when we created the API 23 | audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}', 24 | issuer: '{YOUR-AUTH0-DOMAIN}', 25 | algorithms: ['RS256'] 26 | }); 27 | 28 | app.get('/api/jokes/food', (req, res) => { 29 | let foodJokes = [ 30 | { 31 | id: 99991, 32 | joke: "When Chuck Norris was a baby, he didn't suck his mother's breast. His mother served him whiskey, straight out of the bottle." 33 | }, 34 | { 35 | id: 99992, 36 | joke: 'When Chuck Norris makes a burrito, its main ingredient is real toes.' 37 | }, 38 | { 39 | id: 99993, 40 | joke: 'Chuck Norris eats steak for every single meal. Most times he forgets to kill the cow.' 41 | }, 42 | { 43 | id: 99994, 44 | joke: "Chuck Norris doesn't believe in ravioli. He stuffs a live turtle with beef and smothers it in pig's blood." 45 | }, 46 | { 47 | id: 99995, 48 | joke: "Chuck Norris recently had the idea to sell his urine as a canned beverage. We know this beverage as Red Bull." 49 | }, 50 | { 51 | id: 99996, 52 | joke: 'When Chuck Norris goes to out to eat, he orders a whole chicken, but he only eats its soul.' 53 | } 54 | ]; 55 | res.json(foodJokes); 56 | }) 57 | 58 | app.get('/api/jokes/celebrity', authCheck, (req,res) => { 59 | let CelebrityJokes = [ 60 | { 61 | id: 88881, 62 | joke: 'As President Roosevelt said: "We have nothing to fear but fear itself. And Chuck Norris."' 63 | }, 64 | { 65 | id: 88882, 66 | joke: "Chuck Norris only let's Charlie Sheen think he is winning. Chuck won a long time ago." 67 | }, 68 | { 69 | id: 88883, 70 | joke: 'Everything King Midas touches turnes to gold. Everything Chuck Norris touches turns up dead.' 71 | }, 72 | { 73 | id: 88884, 74 | joke: 'Each time you rate this, Chuck Norris hits Obama with Charlie Sheen and says, "Who is winning now?!"' 75 | }, 76 | { 77 | id: 88885, 78 | joke: "For Charlie Sheen winning is just wishful thinking. For Chuck Norris it's a way of life." 79 | }, 80 | { 81 | id: 88886, 82 | joke: "Hellen Keller's favorite color is Chuck Norris." 83 | } 84 | ]; 85 | res.json(CelebrityJokes); 86 | }) 87 | 88 | app.listen(3333); 89 | console.log('Listening on localhost:3333'); -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .navbar-right { margin-right: 0px !important} 2 | .log { 3 | margin: 5px 10px 0 0; 4 | } -------------------------------------------------------------------------------- /src/components/Callback.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { setIdToken, setAccessToken } from '../utils/AuthService'; 3 | 4 | class Callback extends Component { 5 | 6 | componentDidMount() { 7 | setAccessToken(); 8 | setIdToken(); 9 | window.location.href = "/"; 10 | } 11 | 12 | render() { 13 | return null; 14 | } 15 | } 16 | 17 | export default Callback; 18 | -------------------------------------------------------------------------------- /src/components/CelebrityJokes.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import Nav from './Nav'; 4 | import { getCelebrityData } from '../utils/chucknorris-api'; 5 | 6 | class CelebrityJokes extends Component { 7 | 8 | constructor() { 9 | super(); 10 | this.state = { jokes: [] }; 11 | } 12 | 13 | getCelebrityJokes() { 14 | getCelebrityData().then((jokes) => { 15 | this.setState({ jokes }); 16 | }); 17 | } 18 | 19 | componentDidMount() { 20 | this.getCelebrityJokes(); 21 | } 22 | 23 | render() { 24 | 25 | const { jokes } = this.state; 26 | 27 | return ( 28 |
29 |
53 | ); 54 | } 55 | } 56 | 57 | export default CelebrityJokes; 58 | -------------------------------------------------------------------------------- /src/components/FoodJokes.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import Nav from './Nav'; 4 | import { isLoggedIn } from '../utils/AuthService'; 5 | import { getFoodData } from '../utils/chucknorris-api'; 6 | 7 | 8 | class FoodJokes extends Component { 9 | 10 | constructor() { 11 | super() 12 | this.state = { jokes: [] }; 13 | } 14 | 15 | getFoodJokes() { 16 | getFoodData().then((jokes) => { 17 | this.setState({ jokes }); 18 | }); 19 | } 20 | 21 | componentDidMount() { 22 | this.getFoodJokes(); 23 | } 24 | 25 | render() { 26 | 27 | const { jokes } = this.state; 28 | 29 | return ( 30 |
31 |
57 | ); 58 | } 59 | } 60 | 61 | export default FoodJokes; 62 | -------------------------------------------------------------------------------- /src/components/Nav.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { login, logout, isLoggedIn } from '../utils/AuthService'; 4 | import '../App.css'; 5 | 6 | class Nav extends Component { 7 | 8 | render() { 9 | return ( 10 | 32 | ); 33 | } 34 | } 35 | 36 | export default Nav; 37 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import CelebrityJokes from './components/CelebrityJokes'; 4 | import FoodJokes from './components/FoodJokes'; 5 | import Callback from './components/Callback'; 6 | import { Router, Route, browserHistory } from 'react-router'; 7 | import { requireAuth } from './utils/AuthService'; 8 | 9 | const Root = () => { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 | ) 19 | } 20 | 21 | 22 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/utils/AuthService.js: -------------------------------------------------------------------------------- 1 | import decode from 'jwt-decode'; 2 | import { browserHistory } from 'react-router'; 3 | import auth0 from 'auth0-js'; 4 | const ID_TOKEN_KEY = 'id_token'; 5 | const ACCESS_TOKEN_KEY = 'access_token'; 6 | 7 | const CLIENT_ID = '{AUTH0_CLIENT_ID}'; 8 | const CLIENT_DOMAIN = '{AUTH0_DOMAIN}'; 9 | const REDIRECT = 'YOUR_CALLBACK_URL'; 10 | const SCOPE = '{SCOPE}'; 11 | const AUDIENCE = 'AUDIENCE_ATTRIBUTE'; 12 | 13 | var auth = new auth0.WebAuth({ 14 | clientID: CLIENT_ID, 15 | domain: CLIENT_DOMAIN 16 | }); 17 | 18 | export function login() { 19 | auth.authorize({ 20 | responseType: 'token id_token', 21 | redirectUri: REDIRECT, 22 | audience: AUDIENCE, 23 | scope: SCOPE 24 | }); 25 | } 26 | 27 | export function logout() { 28 | clearIdToken(); 29 | clearAccessToken(); 30 | browserHistory.push('/'); 31 | } 32 | 33 | export function requireAuth(nextState, replace) { 34 | if (!isLoggedIn()) { 35 | replace({pathname: '/'}); 36 | } 37 | } 38 | 39 | export function getIdToken() { 40 | return localStorage.getItem(ID_TOKEN_KEY); 41 | } 42 | 43 | export function getAccessToken() { 44 | return localStorage.getItem(ACCESS_TOKEN_KEY); 45 | } 46 | 47 | function clearIdToken() { 48 | localStorage.removeItem(ID_TOKEN_KEY); 49 | } 50 | 51 | function clearAccessToken() { 52 | localStorage.removeItem(ACCESS_TOKEN_KEY); 53 | } 54 | 55 | // Helper function that will allow us to extract the access_token and id_token 56 | function getParameterByName(name) { 57 | let match = RegExp('[#&]' + name + '=([^&]*)').exec(window.location.hash); 58 | return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); 59 | } 60 | 61 | // Get and store access_token in local storage 62 | export function setAccessToken() { 63 | let accessToken = getParameterByName('access_token'); 64 | localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); 65 | } 66 | 67 | // Get and store id_token in local storage 68 | export function setIdToken() { 69 | let idToken = getParameterByName('id_token'); 70 | localStorage.setItem(ID_TOKEN_KEY, idToken); 71 | } 72 | 73 | export function isLoggedIn() { 74 | const idToken = getIdToken(); 75 | return !!idToken && !isTokenExpired(idToken); 76 | } 77 | 78 | function getTokenExpirationDate(encodedToken) { 79 | const token = decode(encodedToken); 80 | if (!token.exp) { return null; } 81 | 82 | const date = new Date(0); 83 | date.setUTCSeconds(token.exp); 84 | 85 | return date; 86 | } 87 | 88 | function isTokenExpired(token) { 89 | const expirationDate = getTokenExpirationDate(token); 90 | return expirationDate < new Date(); 91 | } -------------------------------------------------------------------------------- /src/utils/chucknorris-api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { getAccessToken } from './AuthService'; 3 | 4 | const BASE_URL = 'http://localhost:3333'; 5 | 6 | export {getFoodData, getCelebrityData}; 7 | 8 | function getFoodData() { 9 | const url = `${BASE_URL}/api/jokes/food`; 10 | return axios.get(url).then(response => response.data); 11 | } 12 | 13 | function getCelebrityData() { 14 | const url = `${BASE_URL}/api/jokes/celebrity`; 15 | return axios.get(url, { headers: { Authorization: `Bearer ${getAccessToken()}` }}).then(response => response.data); 16 | } --------------------------------------------------------------------------------