Skip to content
This repository was archived by the owner on Jul 12, 2020. It is now read-only.

Commit e703525

Browse files
committed
writing documentation.
1 parent abc6b96 commit e703525

File tree

7 files changed

+258
-1
lines changed

7 files changed

+258
-1
lines changed

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
# redux-http-basic-auth-example
1+
# redux-http-basic-auth-example
2+
3+
When a client, web or app, communicates with an HTTP API which enforces some form of authentication, the client typically follows this pattern:
4+
5+
1. The app is not authenticated, the user is presented a log in screen.
6+
2. The user enters their username and password, and taps submit.
7+
3. The credentials are sent to the API and the app inspects the response:
8+
3.1. On success (200 - OK): The authentication token or hash is cached, and the app uses this token in every subsequent request. If at any stage, during an API request, the hash or token doesn't work (401 - Unauthorized) the user needs to be prompted to re-enter their credentials.
9+
3.2. On failure (401 - Unauthorized): The client displays an error message to the user, prompting them re-enter their credentials.

actions/friends.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import fetch from 'isomorphic-fetch'
2+
3+
import { loginFailure } from './user'
4+
5+
export const FRIENDS_REQUEST = 'friends.request'
6+
export const FRIENDS_SUCCESS = 'friends.success'
7+
export const FRIENDS_FAILURE = 'friends.failure'
8+
9+
export function friendsSuccess(friends) {
10+
return {
11+
type: FRIENDS_SUCCESS,
12+
friends
13+
}
14+
}
15+
function friendsRequest() {
16+
return {
17+
type: FRIENDS_REQUEST
18+
}
19+
}
20+
function friendsFailure(error) {
21+
return {
22+
type: FRIENDS_FAILURE,
23+
error
24+
}
25+
}
26+
27+
// Note: This is the same endpoint that we're using for authentication in user.js,
28+
// but pretend that its returning a list of friends.
29+
export function fetchFriends(endpoint = 'basic-auth/admin/secret') {
30+
return (dispatch, getState) => {
31+
32+
dispatch(friendsRequest())
33+
34+
const hash = getState().user.hash
35+
return fetch(`https://httpbin.org/${endpoint}`, {
36+
headers: {
37+
'Authorization': `Basic ${hash}`
38+
}
39+
})
40+
.then(response => response.json().then(json => ({ json, response })))
41+
.then(({json, response}) => {
42+
if (response.ok === false) {
43+
return Promise.reject(response, json)
44+
}
45+
return json
46+
})
47+
.then(
48+
data => {
49+
// data = { friends: [ {}, {}, ... ] }
50+
dispatch(friendsSuccess(data.friends))
51+
},
52+
(response, data) => {
53+
dispatch(friendsFailure(data.error))
54+
55+
// did our request fail because our auth credentials aren't working?
56+
if (response.status == 401) {
57+
dispatch(loginFailure(data.error))
58+
}
59+
}
60+
)
61+
}
62+
}

actions/user.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import fetch from 'isomorphic-fetch'
2+
3+
export const LOGIN_REQUEST = 'login.request'
4+
export const LOGIN_SUCCESS = 'login.success'
5+
export const LOGIN_FAILURE = 'login.failure'
6+
7+
export function loginSuccess(hash, user) {
8+
return {
9+
type: LOGIN_SUCCESS,
10+
hash,
11+
user
12+
}
13+
}
14+
function loginRequest() {
15+
return {
16+
type: LOGIN_REQUEST
17+
}
18+
}
19+
export function loginFailure(error) {
20+
return {
21+
type: LOGIN_FAILURE,
22+
error
23+
}
24+
}
25+
26+
export function login(username, password) {
27+
return (dispatch) => {
28+
29+
dispatch(loginRequest())
30+
31+
// Note: This only works in node.js, use an implementation that works
32+
// for the platform you're using, e.g.: base64-js for React Native, or
33+
// btoa() for browsers.
34+
const hash = new Buffer(`${username}:${password}`).toString('base64')
35+
return fetch('https://httpbin.org/basic-auth/admin/secret', {
36+
headers: {
37+
'Authorization': `Basic ${hash}`
38+
}
39+
})
40+
.then(response => response.json().then(json => ({ json, response })))
41+
.then(({json, response}) => {
42+
if (response.ok === false) {
43+
return Promise.reject(response, json)
44+
}
45+
return json
46+
})
47+
.then(
48+
data => {
49+
// data = { authenticated: true, user: 'admin' }
50+
dispatch(loginSuccess(hash, data.user))
51+
},
52+
(response, data) => dispatch(loginFailure(data.error || 'Log in failed'))
53+
)
54+
}
55+
}

index.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import readlineSync from 'readline-sync'
2+
import { createStore, applyMiddleware, combineReducers } from 'redux'
3+
import thunk from 'redux-thunk'
4+
5+
import { login } from './actions/user'
6+
import { fetchFriends } from './actions/friends'
7+
8+
import userReducer from './reducers/user'
9+
import friendsReducer from './reducers/friends'
10+
const rootReducer = combineReducers({
11+
user: userReducer,
12+
friends: friendsReducer
13+
})
14+
const store = createStore(rootReducer, {}, applyMiddleware(thunk))
15+
16+
17+
18+
store.subscribe(() => {
19+
20+
let state = store.getState()
21+
22+
console.log('---------- STORE STATE ----------')
23+
console.log(JSON.stringify(state, null, 4))
24+
console.log('---------------------------------')
25+
26+
readlineSync.keyInPause()
27+
})
28+
29+
console.log('~~~ Step 1: The user enters a username and password, and taps login.')
30+
store.dispatch(login('admin', 'secret'))

package.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "redux-http-basic-auth-example",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"start:watch": "nodemon index.js --exec babel-node --presets es2015"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/peterp/redux-http-basic-auth-example.git"
12+
},
13+
"author": "",
14+
"license": "ISC",
15+
"bugs": {
16+
"url": "https://github.com/peterp/redux-http-basic-auth-example/issues"
17+
},
18+
"homepage": "https://github.com/peterp/redux-http-basic-auth-example#readme",
19+
"dependencies": {
20+
"isomorphic-fetch": "^2.2.1",
21+
"readline-sync": "^1.4.1",
22+
"redux": "^3.3.1",
23+
"redux-thunk": "^1.0.3"
24+
},
25+
"devDependencies": {
26+
"babel-cli": "^6.6.5",
27+
"babel-preset-es2015": "^6.6.0"
28+
}
29+
}

reducers/friends.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
import {
3+
FRIENDS_REQUEST,
4+
FRIENDS_FAILURE,
5+
FRIENDS_SUCCESS
6+
} from '../actions/friends'
7+
8+
function user(state = {
9+
isLoading: false,
10+
friends: []
11+
}, action) {
12+
switch(action.type) {
13+
case FRIENDS_REQUEST:
14+
return {
15+
isLoading: true,
16+
friends: []
17+
}
18+
case FRIENDS_FAILURE:
19+
return {
20+
isLoading: false,
21+
error: action.error
22+
}
23+
case FRIENDS_SUCCESS:
24+
return {
25+
isLoading: false,
26+
friends: action.friends
27+
}
28+
default:
29+
return state
30+
}
31+
}
32+
33+
export default user

reducers/user.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
// The request, failure, success pattern is very typical when performing
3+
// HTTP requests. The amount of boilerplate can be reduced using Redux
4+
// middleware, _or so I've read._
5+
6+
import {
7+
LOGIN_REQUEST,
8+
LOGIN_FAILURE,
9+
LOGIN_SUCCESS
10+
} from '../actions/user'
11+
12+
function user(state = {
13+
isLoggingIn: false,
14+
isAuthenticated: false
15+
}, action) {
16+
switch(action.type) {
17+
case LOGIN_REQUEST:
18+
return {
19+
isLoggingIn: true,
20+
isAuthenticated: false
21+
}
22+
case LOGIN_FAILURE:
23+
return {
24+
isLoggingIn: false,
25+
isAuthenticated: false,
26+
error: action.error
27+
}
28+
case LOGIN_SUCCESS:
29+
return {
30+
isLoggingIn: false,
31+
isAuthenticated: true,
32+
hash: action.hash,
33+
user: action.user
34+
}
35+
default:
36+
return state
37+
}
38+
}
39+
40+
export default user

0 commit comments

Comments
 (0)