├── .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 | 
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 |
30 |
Privileged Chuck Norris Celebrity Jokes
31 |
32 |
33 | { jokes.map((joke, index) => (
34 |
35 |
36 |
37 |
#{ joke.id }
38 |
39 |
42 |
43 |
44 | ))}
45 |
46 |
47 |
48 |
View Food Jokes
49 | Chuck Norris Food Jokes
50 |
51 |
52 |
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 |
32 |
Chuck Norris Food Jokes
33 |
34 |
35 | { jokes.map((joke, index) => (
36 |
37 |
38 |
39 |
#{ joke.id }
40 |
41 |
44 |
45 |
46 | ))}
47 |
48 |
49 | { isLoggedIn() ?
50 |
51 |
View Celebrity Jokes
52 | Celebrity Jokes
53 | :
Get Access to Celebrity Jokes By Logging In
54 | }
55 |
56 |
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 |
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 | }
--------------------------------------------------------------------------------