Skip to content

Commit acf2f7f

Browse files
committed
add jwt
1 parent 74a3f76 commit acf2f7f

File tree

10 files changed

+183
-49
lines changed

10 files changed

+183
-49
lines changed

chapter10/chapter10.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -668,9 +668,6 @@ To run the task, simply execute `$ grunt` or `$ grunt default`.
668668
The results of running `$ grunt `are shown in Figure 10-3.
669669
670670
![alt](media/image3.png)
671-
672-
673-
674671
***Figure 10-3.** The results of the Grunt default task*
675672
676673
A Brief on Webpack

chapter6/chapter6.md

Lines changed: 113 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
Chapter 6
22
---------
3-
4-
TK JWT
5-
Crypto and Bcrypt,
6-
7-
# Using Sessions and OAuth to Authorize and Authenticate Users in Node.js Apps
3+
# Using Sessions and OAuth to Authorize and Authenticate Users in Node.js Apps
84

95
Security is an important aspect of any real-world web application. This is especially true nowadays, because our apps don’t function in silos anymore. We, as developers, can and should leverage numerous third-party services (e.g., Google, Twitter, GitHub) or become service providers ourselves (e.g., provide a public API).
106

@@ -93,6 +89,115 @@ We just covered token-based authentication, which is often used in REST APIs. Ho
9389

9490
Cookies are similar to tokens, but require less work for us, the developers! This approach is the cornerstone of session-based authentication. The session-based method is the recommended way for basic web apps, because browsers already know what to do with session headers. In addition, in most platforms and frameworks, the session mechanism is built into the core. So, let’s jump straight into session-based authentication with Node.js.
9591

92+
# JSON Web Token (JWT) Authentication
93+
94+
JSON Web Tokens (JWT) allow to send and receive data which is encrypted and has all the necessary information, not just a token such as an API key. Thus, there's no need to store user information on the server. In my opinion, JWT less secure than have the data on the server and only storing the token (API key or session ID) on the client, because while JWT is encrypted anyone can break any encryption given enough time and CPU (albeit it might take 1000s of years).
95+
96+
Nevertheless, JWT is a very common technique to use for implementing web apps. They eliminate the need for the server-side database or store. All info is in this token. It has three parts: header, payload and signature. The encryption method (algorithm) vary depending on what you choose. It can be HS256, RS512, ES384, etc. I'm always paranoid about security so typically the higher the number the better.
97+
98+
To implement a simple JWT login, let's use `jsonwebtoken` library for signing tokens and `bcrypt` for hashing passwords. When client wants to create an account, the system will take the password and hash it asynchronously so not to block the server from processing other requests (the slower the hashing the worse for attackers and the better for you). For example, this is how to get the password from the incoming request body and store the hash into the `users` array:
99+
100+
```js
101+
app.post('/auth/register', (req, res) => {
102+
bcrypt.hash(req.body.password, 10, (error, hash)=>{
103+
if (error) return res.status(500).send()
104+
users.push({
105+
username: req.body.username,
106+
passwordHash: hash
107+
})
108+
res.status(201).send('registered')
109+
})
110+
})
111+
```
112+
113+
114+
Once the user record is created (which has the hash), we can login users to exchange the username and password for the JWT. They'll use this JWT for all other requests like a special key to authenticate and maybe unlock protected and restricted resources (that's authorization because not all users will have access to all the restricted resources).
115+
116+
The GET is not a protected route but POST is a protected route because there's an extra `auth` middleware there which will check for the JWT:
117+
118+
```js
119+
app.get('/courses', (req, res) => {
120+
res.send(courses)
121+
})
122+
app.post('/courses', auth, (req, res) => {
123+
courses.push({title: req.body.title})
124+
res.send(courses)
125+
})
126+
```
127+
128+
The login route checks for the presence of this username in the `users` array but this can be a database call or a call to another API not a simple `find()` method. Next, `bcrypt` has a `compare()` method which asynchronously checks for the hash. If they match (`matched == true`), then `jwt.sign` will issue a signed (encrypted) token which has username in it but can have many other fields not just one field. The `SECRET` string will be populated from the environment variable or from a public key later when the app goes to production. It's a `const` string for now.
129+
130+
```js
131+
app.post('/auth/login', (req, res) => {
132+
const foundUser = users.find((value, index, list) => {
133+
if (value.username === req.body.username) return true
134+
else return false
135+
})
136+
if (foundUser) {
137+
bcrypt.compare(req.body.password, foundUser.passwordHash, (error, matched) => {
138+
if (!error && matched) {
139+
res.status(201).json({token: jwt.sign({ username: foundUser.username}, SECRET)})
140+
} else res.status(401).send()
141+
})
142+
} else res.status(401).send()
143+
})
144+
```
145+
146+
When you get this JWT you can make requests to POST /courses. The `auth` which check for JWT uses the `jwt` module and the data from the headers. The name of the header doesn't matter that much because I set the name myself in the `auth` middleware. Some developers like to use `Authorization` but it's confusing to me since we are not authorizing, but authenticating. The authorization, which is who can do what, is happening in the Node middleware. Here we are performing authentication which is who is this. It's me. Azat Mardan.
147+
148+
```js
149+
const auth = (req, res, next) => {
150+
if (req.headers && req.headers.auth && req.headers.auth.split(' ')[0] === 'JWT') {
151+
jwt.verify(req.headers.auth.split(' ')[1], SECRET, (error, decoded) => {
152+
if (error) return res.status(401).send()
153+
req.user = decoded
154+
console.log('authenticated as ', decoded.username)
155+
next()
156+
})
157+
}
158+
else return res.status(401).send()
159+
}
160+
```
161+
162+
You can play with the full working and tested code in `code/ch6/jwt-example`. I like to use CURL but most of my Node workshop attendees like Postman so in Figure 6-2 I show how to use Postman to extract the JWT (on login). And on Figure 6-3 put it to action (on POST /courses) by pasting the token into the header `auth` after `JTW ` (JWT with a space).
163+
164+
This is how to test the JWT example step by step in Postman (or any other HTTP client):
165+
166+
1. GET /courses will return a list of two courses which are in `server.js`
167+
2. POST /courses with JSON data of `{"title": "blah blah blah"}` will return 401 Not Authorized. Now we know that this is a protected route and we need to create a new user to proceed
168+
3. POST /auth/register with username and password will create a new user as shown in Figure 6-1. Next we can login (sign in) to the server to get the token
169+
4. POST /auth/login with username and password which are matching existing records will return JWT as shown in Figure 6-2
170+
5. POST /courses with title and JWT in the header will allow and create a new course recored (status 201) as shown in Figure 6-3 and Figure 6-4
171+
6. GET /courses will show your new title. Verify it. No need for JWT for this request but it won't hurt either. Figure 6-5.
172+
6. Celebrate and get a cup of tea with a (paleo) cookie.
173+
174+
175+
![Registering a new user by sending JSON payload](media/jwt-1.png)
176+
***Figure 6-1.** Registering a new user by sending JSON payload*
177+
178+
![Logging in to get JWT](media/jwt-2.png)
179+
***Figure 6-2.** Logging in to get JWT *
180+
181+
![Using JWT in the header auth](media/jwt-3.png)
182+
***Figure 6-3.** Using JWT in the header auth*
183+
184+
![200 status for the new course request with JWT in the header and the JSON payload](media/jwt-4.png)
185+
***Figure 6-4.** 200 status for the new course request with JWT in the header and the JSON payload*
186+
187+
![Verifying new course](media/jwt-5.png)
188+
***Figure 6-5.** Verifying new course*
189+
190+
Finally, you can uncheck the `auth` header which has the JWT value and try to make another POST /courses request as shown in Figure 6-6. The request will fail miserably (401) as it should because there's no JWT this time (see `auth` middleware in `server.js`).
191+
192+
![Unchecking auth header with JWT leads to 401 as expected](media/jwt-6.png)
193+
***Figure 6-6.** Unchecking auth header with JWT leads to 401 as expected*
194+
195+
Don't forget to select `raw` and `application/json` when registering (POST /auth/register) and when making other POST requests. And now that you saw and know my password, please don't steal it. (It's not my actual password, but someone used dolphins as a password according to [this pull request "Remove my password from lists so hackers won't be able to hack me"](https://github.com/danielmiessler/SecLists/pull/155)).
196+
197+
JWT is easy to implement. Once on the client after the login request, you can store JWT in local storage or cookies (in the browser) so that your React, Vue, or Angular front-end app can send this token with each request. Protect your secret and pick a strong encryption algorithms to make it harder for attachers to hack your JWT data.
198+
199+
For me sessions are somewhat more secure because I store my data on the server, on encrypted on the client.
200+
96201
# Session-Based Authentication
97202

98203
Session-based authentication is done via the `session` object in the request object `req`. A web session in general is a secure way to store information about a client so that subsequent requests from that same client can be identified.
@@ -499,47 +604,9 @@ Now we can store the bearer for future use and make requests to protected end po
499604
500605
The Everyauth module allows for multiple OAuth strategies to be implemented and added to any Express.js app in just a few lines of code. Everyauth comes with strategies for most of the service providers, so there’s no need to search and implement service provider-specific end points, parameters names, and so forth. Also, Everyauth stores user objects in a session, and database storage can be enabled in a `findOrCreate` callback using a promise pattern.
501606
502-
**Tip** Everyauth has an e-mail and password strategy that can be used instead of the custom-built auth. More information about it can be found in Everyauth documentation at the [GitHub repository](https://github.com/bnoguchi/everyauth#password-authentication) (<https://github.com/bnoguchi/everyauth#password-authentication>)..
503-
504-
Everyauth submodules that enable service provider-specific authorization strategies (as of this writing, take from its [GitHub repo](https://github.com/bnoguchi/everyauth/blob/master/README.md)) (<https://github.com/bnoguchi/everyauth/blob/master/README.md>) are as follows:
505-
506-
- Password
507-
- Facebook
508-
- Twitter
509-
- Google
510-
- Google Hybrid
511-
- LinkedIn
512-
- Dropbox
513-
- Tumblr
514-
- Evernote
515-
- GitHub
516-
- Instagram
517-
- Foursquare
518-
- Yahoo!
519-
- Justin.tv
520-
- Vimeo
521-
- 37signals (Basecamp, Highrise, Backpack, Campfire)
522-
- Readability
523-
- AngelList
524-
- Dwolla
525-
- OpenStreetMap
526-
- VKontakte (Russian Social Network)
527-
- Mail.ru (Russian Social Network)
528-
- Skyrock
529-
- Gowalla
530-
- TripIt
531-
- 500px
532-
- SoundCloud
533-
- mixi
534-
- Mailchimp
535-
- Mendeley
536-
- Stripe
537-
- Datahero
538-
- Salesforce
539-
- Box.net
540-
- OpenId
541-
- LDAP (experimental; not production tested)
542-
- Windows Azure Access Control Service
607+
**Tip** Everyauth has an e-mail and password strategy that can be used instead of the custom-built auth. More information about it can be found in Everyauth documentation at the [GitHub repository](https://github.com/bnoguchi/everyauth#password-authentication) (<https://github.com/bnoguchi/everyauth#password-authentication>).
608+
609+
Everyauth has lots fo submodules which describe how a service might use OAuth exactly. Each one of them might be different. If you just use one of this submodule then you don't need to worry about the details. Instead you just plug in your app secret and client ID and boom! You are rolling. In other words, submodules enable service provider-specific authorization strategies and there are tons of these submodules (strategies): password (simple email and password), Facebook, Twitter, Google, Google Hybrid, LinkedIn, Dropbox, Tumblr, Evernote, GitHub, Instagram, Foursquare, Yahoo!, Justin.tv, Vimeo, 37signals (Basecamp, Highrise, Backpack, Campfire), Readability, AngelList, Dwolla, OpenStreetMap, VKontakte (Russian social network famous for its pirated media), Mail.ru (Russian social network), Skyrock, Gowalla, TripIt, 500px, SoundCloud, mixi, Mailchimp, Mendeley, Stripe, Datahero, Salesforce, Box.net, OpenId, and event LDAP and Windows Azure Access Control Service! and more at <https://github.com/bnoguchi/everyauth/blob/master/README.md>.
543610
544611
# Project: Adding Twitter OAuth 1.0 Sign-in to Blog with Everyauth
545612

chapter6/media/jwt-1.png

110 KB
Loading

chapter6/media/jwt-2.png

136 KB
Loading

chapter6/media/jwt-3.png

124 KB
Loading

chapter6/media/jwt-4.png

134 KB
Loading

chapter6/media/jwt-5.png

144 KB
Loading

chapter6/media/jwt-6.png

130 KB
Loading

code/ch6/jwt-example/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "jwt-example",
3+
"version": "1.0.1",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [],
10+
"author": "Azat Mardan (http://azat.co/)",
11+
"license": "MIT"
12+
}

code/ch6/jwt-example/server.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const SECRET = 'Practical Node, 2nd Edition'
2+
const express = require('express')
3+
const bodyParser = require('body-parser')
4+
const jwt = require('jsonwebtoken')
5+
const bcrypt = require('bcrypt')
6+
const app = express()
7+
app.use(bodyParser.json())
8+
const courses = [
9+
{title: "You Don't Know Node"},
10+
{title: "AWS Intro"}
11+
]
12+
const users = []
13+
const auth = (req, res, next) => {
14+
if (req.headers && req.headers.auth && req.headers.auth.split(' ')[0] === 'JWT') {
15+
jwt.verify(req.headers.auth.split(' ')[1], SECRET, (error, decoded) => {
16+
if (error) return res.status(401).send()
17+
req.user = decoded
18+
console.log('authenticated as ', decoded.username)
19+
next()
20+
})
21+
}
22+
else return res.status(401).send()
23+
}
24+
25+
app.get('/courses', (req, res) => {
26+
res.send(courses)
27+
})
28+
app.post('/courses', auth, (req, res) => {
29+
courses.push({title: req.body.title})
30+
res.send(courses)
31+
})
32+
33+
app.post('/auth/register', (req, res) => {
34+
bcrypt.hash(req.body.password, 10, (error, hash)=>{
35+
if (error) return res.status(500).send()
36+
users.push({
37+
username: req.body.username,
38+
passwordHash: hash
39+
})
40+
res.status(201).send('registered')
41+
})
42+
})
43+
44+
app.post('/auth/login', (req, res) => {
45+
const foundUser = users.find((value, index, list) => {
46+
if (value.username === req.body.username) return true
47+
else return false
48+
})
49+
if (foundUser) {
50+
bcrypt.compare(req.body.password, foundUser.passwordHash, (error, matched) => {
51+
if (!error && matched) {
52+
res.status(201).json({token: jwt.sign({ username: foundUser.username}, SECRET)})
53+
} else res.status(401).send()
54+
})
55+
} else res.status(401).send()
56+
})
57+
58+
app.listen(3000)

0 commit comments

Comments
 (0)