Skip to content

Commit 30b1dcc

Browse files
committed
First commit
0 parents  commit 30b1dcc

17 files changed

+935
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
node_modules

LICENSE.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Auth0, Inc.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+326
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
# Redux JWT Authentication Sample
2+
3+
This is a sample of how to implement JWT authentication in React and [Redux](https://github.com/rackt/redux) apps. It uses Auth0's [NodeJS JWT Authentication Sample](https://github.com/auth0/nodejs-jwt-authentication-sample) to authenticate users and retrieve quotes from a protected endpoint.
4+
5+
The sample is well-informed by the official [Redux examples](https://github.com/rackt/redux/tree/master/examples).
6+
7+
## Installation
8+
9+
Clone the repo and run the installation commands, each in a new terminal window.
10+
11+
```bash
12+
npm run server
13+
14+
# New terminal window
15+
npm start
16+
```
17+
18+
## Important Snippets
19+
20+
Users are authenticated by making a `fetch` request to `localhost:3001/sessions/create`. We have actions setup for this.
21+
22+
```js
23+
// actions.js
24+
25+
// There are three possible states for our login
26+
// process and we need actions for each of them
27+
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
28+
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
29+
export const LOGIN_FAILURE = 'LOGIN_FAILURE'
30+
31+
function requestLogin(creds) {
32+
return {
33+
type: LOGIN_REQUEST,
34+
isFetching: true,
35+
isAuthenticated: false,
36+
creds
37+
}
38+
}
39+
40+
function receiveLogin(user) {
41+
return {
42+
type: LOGIN_SUCCESS,
43+
isFetching: false,
44+
isAuthenticated: true,
45+
id_token: user.id_token
46+
}
47+
}
48+
49+
function loginError(message) {
50+
return {
51+
type: LOGIN_FAILURE,
52+
isFetching: false,
53+
isAuthenticated: false,
54+
message
55+
}
56+
}
57+
58+
// Three possible states for our logout process as well.
59+
// Since we are using JWTs, we just need to remove the token
60+
// from localStorage. These actions are more useful if we
61+
// were calling the API to log the user out
62+
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST'
63+
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
64+
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'
65+
66+
function requestLogout() {
67+
return {
68+
type: LOGOUT_REQUEST,
69+
isFetching: true,
70+
isAuthenticated: true
71+
}
72+
}
73+
74+
function receiveLogout() {
75+
return {
76+
type: LOGOUT_SUCCESS,
77+
isFetching: false,
78+
isAuthenticated: false
79+
}
80+
}
81+
82+
// Calls the API to get a token and
83+
// dispatches actions along the way
84+
export function loginUser(creds) {
85+
86+
let config = {
87+
method: 'POST',
88+
headers: { 'Content-Type':'application/x-www-form-urlencoded' },
89+
body: `username=${creds.username}&password=${creds.password}`
90+
}
91+
92+
return dispatch => {
93+
// We dispatch requestLogin to kickoff the call to the API
94+
dispatch(requestLogin(creds))
95+
return fetch('http://localhost:3001/sessions/create', config)
96+
.then(response =>
97+
response.json()
98+
.then(user => ({ user, response }))
99+
).then(({ user, response }) => {
100+
if (!response.ok) {
101+
// If there was a problem, we want to
102+
// dispatch the error condition
103+
dispatch(loginError(user.message))
104+
return Promise.reject(user)
105+
}
106+
else {
107+
// If login was successful, set the token in local storage
108+
localStorage.setItem('id_token', user.id_token)
109+
110+
// Dispatch the success action
111+
dispatch(receiveLogin(user))
112+
}
113+
}).catch(err => console.log("Error: ", err))
114+
}
115+
}
116+
117+
// Logs the user out
118+
export function logoutUser() {
119+
return dispatch => {
120+
dispatch(requestLogout())
121+
localStorage.removeItem('id_token')
122+
dispatch(receiveLogout())
123+
}
124+
}
125+
```
126+
127+
We also have actions for retreiving the quotes that uses an API middleware.
128+
129+
```js
130+
// middleware/api.js
131+
132+
const BASE_URL = 'http://localhost:3001/api/'
133+
134+
function callApi(endpoint, authenticated) {
135+
136+
let token = localStorage.getItem('id_token') || null
137+
let config = {}
138+
139+
if(authenticated) {
140+
if(token) {
141+
config = {
142+
headers: { 'Authorization': `Bearer ${token}` }
143+
}
144+
} else {
145+
throw "No token saved!"
146+
}
147+
}
148+
149+
return fetch(BASE_URL + endpoint, config)
150+
.then(response =>
151+
response.text()
152+
.then(text => ({ text, response }))
153+
).then(({ text, response }) => {
154+
if (!response.ok) {
155+
return Promise.reject(text)
156+
}
157+
158+
return text
159+
}).catch(err => console.log(err))
160+
}
161+
162+
export const CALL_API = Symbol('Call API')
163+
164+
export default store => next => action => {
165+
166+
const callAPI = action[CALL_API]
167+
168+
// So the middleware doesn't get applied to every single action
169+
if (typeof callAPI === 'undefined') {
170+
return next(action)
171+
}
172+
173+
let { endpoint, types, authenticated } = callAPI
174+
175+
const [ requestType, successType, errorType ] = types
176+
177+
// Passing the authenticated boolean back in our data will let us distinguish between normal and secret quotes
178+
return callApi(endpoint, authenticated).then(
179+
response =>
180+
next({
181+
response,
182+
authenticated,
183+
type: successType
184+
}),
185+
error => next({
186+
error: error.message || 'There was an error.',
187+
type: errorType
188+
})
189+
)
190+
}
191+
```
192+
193+
```js
194+
// actions.js
195+
196+
// Uses the API middlware to get a quote
197+
export function fetchQuote() {
198+
return {
199+
[CALL_API]: {
200+
endpoint: 'random-quote',
201+
types: [QUOTE_REQUEST, QUOTE_SUCCESS, QUOTE_FAILURE]
202+
}
203+
}
204+
}
205+
206+
// Same API middlware is used to get a
207+
// secret quote, but we set authenticated
208+
// to true so that the auth header is sent
209+
export function fetchSecretQuote() {
210+
return {
211+
[CALL_API]: {
212+
endpoint: 'protected/random-quote',
213+
authenticated: true,
214+
types: [QUOTE_REQUEST, QUOTE_SUCCESS, QUOTE_FAILURE]
215+
}
216+
}
217+
}
218+
```
219+
220+
The reducers return new objects with the data carried by the actions.
221+
222+
```js
223+
// reducers.js
224+
225+
import { combineReducers } from 'redux'
226+
import {
227+
LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT_SUCCESS,
228+
QUOTE_REQUEST, QUOTE_SUCCESS, QUOTE_FAILURE
229+
} from './actions'
230+
231+
// The auth reducer. The starting state sets authentication
232+
// based on a token being in local storage. In a real app,
233+
// we would also want a util to check if the token is expired.
234+
function auth(state = {
235+
isFetching: false,
236+
isAuthenticated: localStorage.getItem('id_token') ? true : false
237+
}, action) {
238+
switch (action.type) {
239+
case LOGIN_REQUEST:
240+
return Object.assign({}, state, {
241+
isFetching: true,
242+
isAuthenticated: false,
243+
user: action.creds
244+
})
245+
case LOGIN_SUCCESS:
246+
return Object.assign({}, state, {
247+
isFetching: false,
248+
isAuthenticated: true,
249+
errorMessage: ''
250+
})
251+
case LOGIN_FAILURE:
252+
return Object.assign({}, state, {
253+
isFetching: false,
254+
isAuthenticated: false,
255+
errorMessage: action.message
256+
})
257+
case LOGOUT_SUCCESS:
258+
return Object.assign({}, state, {
259+
isFetching: true,
260+
isAuthenticated: false
261+
})
262+
default:
263+
return state
264+
}
265+
}
266+
267+
// The quotes reducer
268+
function quotes(state = {
269+
isFetching: false,
270+
quote: '',
271+
authenticated: false
272+
}, action) {
273+
switch (action.type) {
274+
case QUOTE_REQUEST:
275+
return Object.assign({}, state, {
276+
isFetching: true
277+
})
278+
case QUOTE_SUCCESS:
279+
return Object.assign({}, state, {
280+
isFetching: false,
281+
quote: action.response,
282+
authenticated: action.authenticated || false
283+
})
284+
case QUOTE_FAILURE:
285+
return Object.assign({}, state, {
286+
isFetching: false
287+
})
288+
default:
289+
return state
290+
}
291+
}
292+
293+
// We combine the reducers here so that they
294+
// can be left split apart above
295+
const quotesApp = combineReducers({
296+
auth,
297+
quotes
298+
})
299+
300+
export default quotesApp
301+
```
302+
303+
## License
304+
305+
MIT
306+
307+
## What is Auth0?
308+
309+
Auth0 helps you to:
310+
311+
* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, amont others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**.
312+
* Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**.
313+
* Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user.
314+
* Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely.
315+
* Analytics of how, when and where users are logging in.
316+
* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules).
317+
318+
## Create a Free Auth0 Account
319+
320+
1. Go to [Auth0](https://auth0.com) and click Sign Up.
321+
2. Use Google, GitHub or Microsoft Account to login.
322+
323+
## Author
324+
325+
[Auth0](https://auth0.com)
326+

0 commit comments

Comments
 (0)