Skip to content

Commit 3554825

Browse files
[mabel] Modify security jwt notes
1 parent ea43784 commit 3554825

File tree

2 files changed

+40
-43
lines changed

2 files changed

+40
-43
lines changed

docs/backend-web-development/mongoose-middleware.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
# Mongoose middleware
22

3-
Middleware are functions that run at specific stages of a pipeline. Mongoose supports middleware for the following operations:
3+
Middleware are functions that run at specific stages of a pipeline. For Mongoose, [middleware is specified on the schema level](https://mongoosejs.com/docs/middleware.html).
4+
5+
Mongoose has 4 types of middleware.
46

57
Aggregate
68
Document
79
Model
810
Query
911

10-
For instance, models have `pre` and `post` functions that take two parameters:
12+
For instance, for document middleware, the `pre` and `post` functions will take two parameters:
13+
14+
- Type of event ('init', 'validate', 'save', 'remove', 'updateOne' and 'deleteOne' )
15+
- A callback that is executed with `this` referencing the document
1116

12-
- Type of event (‘init’, ‘validate’, ‘save’, ‘remove’)
13-
- A callback that is executed with this referencing the model instance
17+
All middleware types support pre and post hooks.
1418

15-
https://www.freecodecamp.org/news/introduction-to-mongoose-for-mongodb-d2a7aa593c57/
19+
Also see https://www.freecodecamp.org/news/introduction-to-mongoose-for-mongodb-d2a7aa593c57/.
1620

1721
## Hashing password with bcrypt
1822

1923
Hashing user's password with bcrypt before saving to database.
2024

2125
```js
22-
userSchema.pre("save", async function (next) {
26+
userSchema.pre("save", async function () {
2327
if (this.isModified("password")) {
2428
const rounds = 10;
2529
this.password = await bcrypt.hash(this.password, rounds);
26-
next();
2730
}
2831
});
2932
```

docs/backend-web-development/security-jwt.md

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ Decoding it using base64url, we can see the plaintext of the three parts.
5656
We have been using this term a few times above. What exactly is this thing? Why do we need it?
5757
This is one algorithm that converts binary data (or text data) into a format that can be carried in HTTP request URL or headers.
5858

59-
According to the specification of HTTP protocol, there are certain characters (such as + and =) that are not allowed to apppar as part of URL or request/response header. On the other hand, people usually include JWT tokens as part of their HTTP requests (as we will show below), so it's important to make sure JWT token value do not contain those forbidden characters.
59+
According to the specification of HTTP protocol, there are certain characters (such as + and =) that are not allowed to apppar as part of URL or request/response header. Both characters have a special meaning in the URI address: “+” is interpreted as space, while “=” is used to send data via query string as “key=value” pair.
60+
61+
On the other hand, people often include JWT tokens as part of their HTTP requests (as we will show below), so it's important to make sure JWT token value do not contain those forbidden characters.
6062

6163
People created this base64url encoding scheme for this purpose.
6264

@@ -66,7 +68,7 @@ Base64url is an **encoding** scheme, not an **encryption** scheme. It is easily
6668

6769
## Why have a signature? What is RS256 or HS256?
6870

69-
Text from https://community.auth0.com/t/jwt-signing-algorithms-rs256-vs-hs256/7720
71+
Adapted from https://community.auth0.com/t/jwt-signing-algorithms-rs256-vs-hs256/7720
7072

7173
> Both choices refer to what algorithm the identity provider uses to sign the JWT. Signing is a cryptographic operation that generates a “signature” (part of the JWT) that the recipient of the token can validate to ensure that the token has not been tampered with.
7274
>
@@ -76,15 +78,15 @@ Text from https://community.auth0.com/t/jwt-signing-algorithms-rs256-vs-hs256/77
7678
7779
### A good secret for HS256
7880

79-
Text from https://auth0.com/blog/a-look-at-the-latest-draft-for-jwt-bcp/
81+
Text adapted from https://auth0.com/blog/a-look-at-the-latest-draft-for-jwt-bcp/
8082

8183
[JSON Web Algorithms](https://tools.ietf.org/html/rfc7518) defines the minimum key length to be equal to the size in bits of the hash function used along with the HMAC algorithm:
8284

8385
> "A key of the same size as the hash output (for instance, 256 bits for "HS256") or larger MUST be used with this algorithm." - JSON Web Algorithms (RFC 7518), 3.2 HMAC with SHA-2 Functions
8486
8587
If a short key like `secret` (which is ironically very not secret!) is used, we can use [brute force attacks to guess the key](https://auth0.com/blog/brute-forcing-hs256-is-possible-the-importance-of-using-strong-keys-to-sign-jwts/).
8688

87-
Alternatively, use HS256.
89+
Alternatively, use RS256.
8890

8991
## Using JWT for http request authentication and authorization
9092

@@ -125,29 +127,11 @@ For us to read cookies, we need `cookie-parser`.
125127
npm install cookie-parser
126128
```
127129

128-
For front-end to use this backend, we use `cors`.
129-
Same origin policy, see https://jonhilton.net/cross-origin-request-blocked/
130-
131-
`cors` helps us to efficiently handle cross domain requests.
132-
133-
```
134-
npm install cors
135-
```
136-
137-
In app.js, we use these middleware.
130+
In app.js, we use this middleware.
138131

139132
```js
140133
// app.js
141134
const cookieParser = require("cookie-parser");
142-
const cors = require("cors");
143-
144-
const corsOptions = {
145-
credentials: true,
146-
allowedHeaders: "content-type",
147-
origin: "http://localhost:3001",
148-
};
149-
150-
app.use(cors(corsOptions));
151135

152136
app.use(cookieParser());
153137
```
@@ -179,7 +163,6 @@ const trainerSchema = new mongoose.Schema({
179163
username: {
180164
type: String,
181165
required: true,
182-
index: true, // helps us to find by username, note that this has a significant production impact
183166
unique: true,
184167
minlength: 3,
185168
lowercase: true,
@@ -197,18 +180,19 @@ const trainerSchema = new mongoose.Schema({
197180
},
198181
});
199182

200-
trainerSchema.virtual("fullName").get(function() {
183+
trainerSchema.virtual("fullName").get(function () {
201184
return `${this.salutation} ${this.firstName} ${this.lastName}`;
202185
});
203186

204-
trainerSchema.virtual("reverseName").get(function() {
187+
trainerSchema.virtual("reverseName").get(function () {
205188
return `${this.lastName}, ${this.firstName}`;
206189
});
207190

208-
trainerSchema.pre("save", async function(next) {
209-
const rounds = 10;
210-
this.password = await bcrypt.hash(this.password, rounds);
211-
next();
191+
trainerSchema.pre("save", async function () {
192+
if (this.isModified("password")) {
193+
const rounds = 10;
194+
this.password = await bcrypt.hash(this.password, rounds);
195+
}
212196
});
213197

214198
const Trainer = mongoose.model("Trainer", trainerSchema);
@@ -222,7 +206,6 @@ module.exports = Trainer;
222206
router.post("/", async (req, res, next) => {
223207
try {
224208
const trainer = new Trainer(req.body);
225-
await Trainer.init();
226209
const newTrainer = await trainer.save();
227210
res.send(newTrainer);
228211
} catch (err) {
@@ -283,12 +266,12 @@ const getJWTSecret = () => {
283266
return secret;
284267
};
285268

286-
const createJWTToken = username => {
269+
const createJWTToken = (username) => {
287270
const today = new Date();
288271
const exp = new Date(today);
289272

290273
const secret = getJWTSecret();
291-
exp.setDate(today.getDate() + 60);
274+
exp.setDate(today.getDate() + 60); // adding days
292275

293276
const payload = { name: username, exp: parseInt(exp.getTime() / 1000) };
294277
const token = jwt.sign(payload, secret);
@@ -345,11 +328,10 @@ router.post("/login", async (req, res, next) => {
345328
// Can expiry date on cookie be changed? How about JWT token?
346329
res.cookie("token", token, {
347330
expires: expiryDate,
348-
httpOnly: true,
331+
httpOnly: true, // client-side js cannot access cookie info
332+
secure: true, // use HTTPS
349333
});
350334

351-
// why can't we have secure: true?
352-
353335
res.send("You are now logged in!");
354336
} catch (err) {
355337
if (err.message === "Login failed") {
@@ -368,6 +350,14 @@ You can generate a good random 256 bits key (crypographically strong pseudorando
368350
node -e "console.log(require('crypto').randomBytes(256 / 8).toString('hex'));"
369351
```
370352

353+
You can also generate a base64 key with:
354+
355+
```sh
356+
node -e "console.log(require('crypto').randomBytes(256 / 8).toString('base64'));"
357+
```
358+
359+
If you choose to use a base64 key, read the key into a Buffer using `Buffer.from(key, "base64")` and use it with `jwt.sign` and `jwt.verify`.
360+
371361
Save it in `.env` file and do not commit it. Remember to add the `.env` file to `.gitignore`.
372362

373363
## Security Concerns of using JWT
@@ -428,3 +418,7 @@ This solution works, however, if you do this, there is not much benefit of using
428418

429419
If you use JWT token for session tracking, all the session information is in the JWT token. When a user logout, your client side application needs to remove this token from its memory.
430420
If the JWT token is saved in a cookie, the logout route handler on the server side needs to delete the cookie that stores JWT token upon user logout. That can be done via the response.clearCookie() provided by Express framework.
421+
422+
## Exercises
423+
424+
Add `user` model to your songs API and protect the routes for PUT and DELETE. You will need to login to PUT and DELETE any of the songs.

0 commit comments

Comments
 (0)