Skip to content

Commit 316f10a

Browse files
authored
Merge pull request #3 from node-oauth/server-to-server-express
Server to server express example
2 parents b6aa3df + f938ad5 commit 316f10a

File tree

14 files changed

+2929
-2
lines changed

14 files changed

+2929
-2
lines changed

.gitignore

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
9+
# Diagnostic reports (https://nodejs.org/api/report.html)
10+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11+
12+
# Runtime data
13+
pids
14+
*.pid
15+
*.seed
16+
*.pid.lock
17+
18+
# Directory for instrumented libs generated by jscoverage/JSCover
19+
lib-cov
20+
21+
# Coverage directory used by tools like istanbul
22+
coverage
23+
*.lcov
24+
25+
# nyc test coverage
26+
.nyc_output
27+
28+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29+
.grunt
30+
31+
# Bower dependency directory (https://bower.io/)
32+
bower_components
33+
34+
# node-waf configuration
35+
.lock-wscript
36+
37+
# Compiled binary addons (https://nodejs.org/api/addons.html)
38+
build/Release
39+
40+
# Dependency directories
41+
node_modules/
42+
jspm_packages/
43+
44+
# TypeScript v1 declaration files
45+
typings/
46+
47+
# TypeScript cache
48+
*.tsbuildinfo
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Microbundle cache
57+
.rpt2_cache/
58+
.rts2_cache_cjs/
59+
.rts2_cache_es/
60+
.rts2_cache_umd/
61+
62+
# Optional REPL history
63+
.node_repl_history
64+
65+
# Output of 'npm pack'
66+
*.tgz
67+
68+
# Yarn Integrity file
69+
.yarn-integrity
70+
71+
# dotenv environment variables file
72+
.env
73+
.env.test
74+
75+
# parcel-bundler cache (https://parceljs.org/)
76+
.cache
77+
78+
# Next.js build output
79+
.next
80+
81+
# Nuxt.js build / generate output
82+
.nuxt
83+
# dist
84+
85+
# Gatsby files
86+
.cache/
87+
# Comment in the public line in if your project uses Gatsby and *not* Next.js
88+
# https://nextjs.org/blog/next-9-1#public-directory-support
89+
# public
90+
91+
# vuepress build output
92+
.vuepress/dist
93+
94+
# Serverless directories
95+
.serverless/
96+
97+
# FuseBox cache
98+
.fusebox/
99+
100+
# DynamoDB Local files
101+
.dynamodb/
102+
103+
# TernJS port file
104+
.tern-port
105+
106+
# IDE specific
107+
.idea
108+
.vscode
109+
.atom

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,19 @@
1-
# node-oauth2-server-examples
2-
This repository is meant to maintain a list of examples of using node-oauth2-server with different databases.
1+
# Node OAuth2 Server Examples
2+
3+
This repository is meant to maintain a list of examples of using @node-oauth/oauth2-server
4+
with different setups.
5+
6+
## Examples
7+
8+
Each example contains its own README with specific installation and run instructions.
9+
10+
### Server to server
11+
12+
Location: [/server2server](./server2server)
13+
14+
Minimal client-credential grant flow with express and fetch and in-memory DB.
15+
Suitable for (Micro-) services and small apps, exchanging protected resources.
16+
17+
## License
18+
19+
All examples are MIT licenses, defined by the top-level [LICENSE](./LICENSE) file

server2server/.eslintrc

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"extends": "eslint:recommended",
3+
"env": {
4+
"node": true,
5+
"mocha": true,
6+
"es6": true
7+
},
8+
"parserOptions": {
9+
"ecmaVersion": 9,
10+
"sourceType": "module",
11+
"ecmaFeatures" : {
12+
"globalReturn": false,
13+
"impliedStrict": true,
14+
"jsx": false
15+
}
16+
},
17+
"rules": {
18+
"indent": [
19+
"error",
20+
2
21+
],
22+
"linebreak-style": [
23+
"error",
24+
"unix"
25+
],
26+
"quotes": [
27+
"error",
28+
"single"
29+
],
30+
"semi": [
31+
"error",
32+
"always"
33+
],
34+
"no-var": [
35+
"error"
36+
],
37+
"prefer-const": ["error", {
38+
"destructuring": "any",
39+
"ignoreReadBeforeAssign": false
40+
}],
41+
"no-unused-vars": [
42+
"error",
43+
{
44+
"vars": "all",
45+
"args": "none",
46+
"ignoreRestSiblings": false
47+
}
48+
]
49+
}
50+
}
51+

server2server/README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Server 2 server with Client Credentials Grant
2+
3+
## Architecture
4+
5+
The client credentials workflow is described in
6+
[RFC 6749, section 4.4](https://datatracker.ietf.org/doc/html/rfc6749.html#section-4.4):
7+
8+
```
9+
+---------+ +---------------+
10+
| | | |
11+
| |>--(A)- Client Authentication --->| Authorization |
12+
| Client | | Server |
13+
| |<--(B)---- Access Token ---------<| |
14+
| | | |
15+
+---------+ +---------------+
16+
```
17+
18+
In this setup the authorization server does also contain the consumable **resources**.
19+
Therefore, the setup looks something like this:
20+
21+
```
22+
+----------+ +--------------+
23+
| | | |
24+
| Consumer |>--(A)- Client Authentication --->| Provider |
25+
| | | |
26+
| |<--(B)---- Access Token ---------<| |
27+
| | | |
28+
| |>--(C)---- Access resource ---------> [Resource] |
29+
| | | | |
30+
| |<--(D)----Resource response -----<--------< |
31+
+----------+ +--------------+
32+
```
33+
34+
### Provider dependencies
35+
- @node-oauth/express-oauth-server (uses @node-oauth/oauth2-server)
36+
- express
37+
- body-parser
38+
- dotenv
39+
40+
### Consumer dependencies
41+
42+
- node-fetch
43+
- dotenv
44+
45+
## Installation and usage
46+
47+
If you haven't already cloned this repository, then clone it via
48+
49+
```shell
50+
$ git clone https://github.com/node-oauth/node-oauth2-server-examples.git
51+
$ cd server2server
52+
```
53+
54+
### Install and run the provider
55+
56+
Since we have two servers you need to install dependencies in both.
57+
First, start with the provider:
58+
59+
```shell
60+
$ cd provider
61+
$ npm install
62+
$ npm run start
63+
```
64+
65+
The provider runs on `http://localhost:8080`
66+
67+
68+
### Install and run the consumer
69+
70+
```shell
71+
$ cd ../consumer
72+
$ npm install
73+
$ npm run start
74+
```
75+
76+
The consumer will now make several requests. Note, that some of them are expected to fail.
77+
The overall output should look like so:
78+
79+
```shell
80+
[Consumer]: get /public (public)
81+
[Consumer]: => response: 200 OK moo
82+
83+
[Consumer]: get /read-resource (not authenticated)
84+
[Consumer]: => response: 401 Unauthorized
85+
86+
[Consumer]: post /token (bad credentials)
87+
[Consumer]: => response: 401 Unauthorized {"error":"invalid_client","error_description":"Invalid client: client is invalid"}
88+
89+
[Consumer]: post /token (bad credentials)
90+
[Consumer]: => response: 200 OK {"access_token":"45f81685482d6e1337b99ddb8726b7c04355b3d427b1401cf08e5c3bea013a38","token_type":"Bearer","expires_in":3600,"scope":true}
91+
92+
[Consumer]: authorization token successfully retrieved!
93+
94+
[Consumer]: get /read-resource (authenticated, resource is not yet defined)
95+
[Consumer]: => response: 200 OK {"resource":null}
96+
97+
[Consumer]: post /write-resource (authentication failed)
98+
[Consumer]: => response: 401 Unauthorized {"error":"invalid_token","error_description":"Invalid token: access token is invalid"}
99+
100+
[Consumer]: post /write-resource (Invalid token)
101+
[Consumer]: => response: 200 OK {"message":"resource created"}
102+
103+
[Consumer]: get /read-resource (authenticated, resource is now)
104+
[Consumer]: => response: 200 OK {"resource":"foo-bar-moo"}
105+
```
106+
107+
## How routes are protected
108+
109+
If you take a look at the `provider/index.js` file, you will see a mix of public and private routes.
110+
111+
```js
112+
app.get('/protected-route', oauth.authenticate(), function (req, res) {
113+
res.send({ resource: internal.resource });
114+
})
115+
```
116+
117+
If the authenticated middleware fails, there will be no `next()` call into your provided handler function.
118+
The authentication will fail, if no **access token** is provided.
119+
120+
In order to receive an access token, the client needs to make a call to the **public** `/token` endpoint, first:
121+
122+
```js
123+
const tokenBodyParams = new URLSearchParams();
124+
tokenBodyParams.append('grant_type', 'client_credentials');
125+
tokenBodyParams.append('scope', 'read');
126+
127+
const response = await fetch('http://localhost:8080/token', {
128+
method: 'post',
129+
body: tokenBodyParams,
130+
headers: {
131+
'content-type': 'application/x-www-form-urlencoded',
132+
'authorization': 'Basic ' + Buffer.from(`${client.id}:${client.secret}`).toString('base64'),
133+
}
134+
})
135+
const token = await response.json()
136+
const accessToken = token.access_token
137+
const tokenType = token.token_type
138+
```
139+
140+
With this token, the client can make authenticated requests to the protected endpoints:
141+
142+
```js
143+
const response = await fetch('http://localhost:8080/read-resource', {
144+
method: 'get',
145+
headers: {
146+
'authorization': `${tokenType} ${accessToken}`
147+
}
148+
})
149+
const resource = await response.json()
150+
```
151+
152+
Since there are no refresh token involved, the requests may fail, due to expired token.
153+
It's up to the client to re-request a new token.

server2server/consumer/.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CLIENT_ID="this-client-id-is-for-demo-only"
2+
CLIENT_SECRET="this-secret-id-is-for-demo-only"

0 commit comments

Comments
 (0)