Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 38c5d1d

Browse files
committed
implement client-sessions-based (cookie) authentication app + sample requests
1 parent 2721eb4 commit 38c5d1d

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

README.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,124 @@
11
# Mock Express request/response with Jest or sinon
2+
3+
## Requests
4+
5+
### Login
6+
7+
```sh
8+
curl --request POST \
9+
--url http://localhost:3000/session \
10+
--header 'content-type: application/json' \
11+
--data '{
12+
"username": "hugo",
13+
"password": "boss"
14+
}' -v
15+
```
16+
17+
Sample Successful (200) Response:
18+
19+
```sh
20+
> POST /session HTTP/1.1
21+
> Host: localhost:3000
22+
> User-Agent: curl/7.54.0
23+
> Accept: */*
24+
> content-type: application/json
25+
> Content-Length: 58
26+
>
27+
* upload completely sent off: 58 out of 58 bytes
28+
< HTTP/1.1 201 Created
29+
< X-Powered-By: Express
30+
< Content-Type: application/json; charset=utf-8
31+
< Set-Cookie: session=t_4OrqgrscRYVgGwtN0EMg.WmpPuJSiukSgV0iWS7oqg6a9rfsDTbtLcoQQiRkJyydfOjOI8HE9dP2kzcfTmRqR.1550427342962.3600000.Xajry447dwhSnzt1mXYN9SoYzd3PjTyo_Dwli5IrK6Y; path=/; expires=Fri, 15 Feb 2019 19:15:43 GMT; httponly
32+
< Date: Fri, 15 Feb 2019 18:15:42 GMT
33+
< Connection: keep-alive
34+
< Content-Length: 0
35+
```
36+
37+
What interests us is `Set-Cookie: session=t_4OrqgrscRYVgGwtN0EMg...` (truncated for readability).
38+
39+
This is an encrypted session (as created by `client-sessions`) contained in the `session` cookie.
40+
41+
### Logout
42+
43+
```sh
44+
curl --request DELETE \
45+
--url http://localhost:3000/session \
46+
--cookie session=*INSERT_OUTPUT_OF_SET_COOKIE_SESSION_LOGIN_REQUEST* \
47+
-v
48+
```
49+
50+
Sample Successful Response:
51+
```sh
52+
> DELETE /session HTTP/1.1
53+
> Host: localhost:3000
54+
> User-Agent: curl/7.54.0
55+
> Accept: */*
56+
> Cookie: session=t_4OrqgrscRYVgGwtN0EMg.WmpPuJSiukSgV0iWS7oqg6a9rfsDTbtLcoQQiRkJyydfOjOI8HE9dP2kzcfTmRqR.1550427342962.3600000.Xajry447dwhSnzt1mXYN9SoYzd3PjTyo_Dwli5IrK6Y
57+
>
58+
< HTTP/1.1 200 OK
59+
< X-Powered-By: Express
60+
< Content-Type: application/json; charset=utf-8
61+
< Set-Cookie: session=97I-bC6WbilzHbqLhPJevg.vMfAWQscH6PChT-elMcYqy3vwtLcxKtTZ16X1abANHo.1550427342962.3600000.H6y03kGPA0Nd8sIJqDQHaOn4Rb377NOtOEGuGz9Ecu0; path=/; expires=Fri, 15 Feb 2019 19:15:43 GMT; httponly
62+
< Date: Fri, 15 Feb 2019 18:19:13 GMT
63+
< Connection: keep-alive
64+
< Content-Length: 0
65+
<
66+
```
67+
68+
Again the interesting part of the response is `Set-Cookie: session=97I-bC6WbilzHbqLhPJevg.vMfAWQscH6PChT...` (truncated).
69+
70+
What the application code does is not actually clear the cookie, but override the contents of the cookie.
71+
72+
Therefore it sends back a `Set-Cookie` with this updated "session" (which is empty and `GET /session` using it will 401).
73+
74+
### Check
75+
76+
```sh
77+
curl --request GET \
78+
--url http://localhost:3000/session \
79+
--cookie session=*INSERT_OUTPUT_OF_SET_COOKIE_SESSION_LOGIN_REQUEST* \
80+
-v
81+
```
82+
83+
Sample Successful (200) Response:
84+
85+
```sh
86+
> GET /session HTTP/1.1
87+
> Host: localhost:3000
88+
> User-Agent: curl/7.54.0
89+
> Accept: */*
90+
> Cookie: session=t_4OrqgrscRYVgGwtN0EMg.WmpPuJSiukSgV0iWS7oqg6a9rfsDTbtLcoQQiRkJyydfOjOI8HE9dP2kzcfTmRqR.1550427342962.3600000.Xajry447dwhSnzt1mXYN9SoYzd3PjTyo_Dwli5IrK6Y
91+
>
92+
< HTTP/1.1 200 OK
93+
< X-Powered-By: Express
94+
< Content-Type: application/json; charset=utf-8
95+
< Content-Length: 19
96+
< ETag: W/"13-NGIK6C7P0giZ5uHUWH1fsFMw4TY"
97+
< Date: Sun, 17 Feb 2019 18:23:33 GMT
98+
< Connection: keep-alive
99+
<
100+
101+
{"username":"hugo"}
102+
```
103+
104+
It reflects the username back to us from the session cookie.
105+
106+
Sample Fail (401) Response:
107+
108+
```sh
109+
> GET /session HTTP/1.1
110+
> Host: localhost:3000
111+
> User-Agent: curl/7.54.0
112+
> Accept: */*
113+
> Cookie: session=97I-bC6WbilzHbqLhPJevg.vMfAWQscH6PChT-elMcYqy3vwtLcxKtTZ16X1abANHo.1550427342962.3600000.H6y03kGPA0Nd8sIJqDQHaOn4Rb377NOtOEGuGz9Ecu0
114+
>
115+
< HTTP/1.1 401 Unauthorized
116+
< X-Powered-By: Express
117+
< Content-Type: application/json; charset=utf-8
118+
< Date: Sun, 17 Feb 2019 18:25:38 GMT
119+
< Connection: keep-alive
120+
< Content-Length: 0
121+
<
122+
```
123+
124+
We're just interested in the 401 here :+1:.

app.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const express = require('express');
2+
const bodyParser = require('body-parser')
3+
const session = require('client-sessions')
4+
5+
const app = express();
6+
7+
app.use(bodyParser.json());
8+
app.use(session({
9+
secret: process.env.SESSION_SECRET || 'my-super-secret',
10+
cookieName: 'session',
11+
duration: 60 * 60 * 1000 // 1 hour
12+
}))
13+
14+
const { login, logout, checkAuth } = require('./express-handlers')
15+
16+
app.post('/session', login)
17+
app.delete('/session', logout)
18+
app.get('/session', checkAuth)
19+
20+
const PORT = process.env.PORT || 3000
21+
app.listen(PORT, () => {
22+
console.log(`Server listening on http://localhost:${PORT}`);
23+
});

express-handlers.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const bcrypt = require('bcrypt');
2+
3+
const users = [
4+
{
5+
name: 'hugo',
6+
// generated from 'boss' with bcrypt
7+
// using work factor 10
8+
password: '$2a$10$IYTsvP51gvUfM2SvZ47acekm05qdyxQbVW5Yy2q3dPp1EipWx7clm'
9+
},
10+
{
11+
name: 'guest',
12+
// generated from 'guest-boss' with bcrypt
13+
// using work factor 10
14+
password: '$2a$10$6rfA.JiURAnuGhVAKpaoneXhsOuKBBRfKDRUgfLxMnVvQUWK5u6h2'
15+
}
16+
];
17+
18+
function getUser(username) {
19+
return users.find(({ name }) => name === username);
20+
}
21+
22+
async function login(req, res) {
23+
try {
24+
const { username, password } = req.body
25+
if (!username || !password) {
26+
return res.status(400).json({ message: 'username and password are required' });
27+
}
28+
const user = getUser(username);
29+
if (!user) {
30+
return res.status(401).json({ message: 'No user with matching username' });
31+
}
32+
if (!(await bcrypt.compare(password, user.password))) {
33+
return res.status(401).json({ message: 'Wrong password' });
34+
}
35+
36+
req.session.data = { username };
37+
return res.status(201).json();
38+
} catch (e) {
39+
console.error(`Error during login of "${req.body.username}": ${e.stack}`);
40+
res.status(500).json({ message: e.message });
41+
}
42+
}
43+
44+
async function logout(req, res) {
45+
req.session.data = null;
46+
return res.status(200).json();
47+
}
48+
49+
async function checkAuth(req, res) {
50+
if (!req.session.data) {
51+
return res.status(401).json();
52+
}
53+
const { username } = req.session.data;
54+
return res.status(200).json({ username });
55+
}
56+
57+
module.exports = {
58+
login,
59+
logout,
60+
checkAuth,
61+
};

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"description": "",
55
"main": "index.js",
66
"scripts": {
7+
"start": "node app.js",
78
"test": "ava && jest"
89
},
910
"keywords": [],
@@ -17,6 +18,7 @@
1718
},
1819
"dependencies": {
1920
"bcrypt": "^3.0.4",
21+
"body-parser": "^1.18.3",
2022
"client-sessions": "^0.8.0",
2123
"express": "^4.16.4"
2224
},

0 commit comments

Comments
 (0)