Skip to content

Commit 58b1138

Browse files
Added authoriation code example.
1 parent 508ac98 commit 58b1138

File tree

13 files changed

+989
-0
lines changed

13 files changed

+989
-0
lines changed

authorization-code/.eslintrc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
}

authorization-code/README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Authorization Code Grant Example
2+
3+
## Architecture
4+
5+
The authorization code workflow is described in
6+
[RFC 6749, section 4.1](https://datatracker.ietf.org/doc/html/rfc6749.html#section-4.1):
7+
8+
```
9+
+----------+
10+
| Resource |
11+
| Owner |
12+
| |
13+
+----------+
14+
^
15+
|
16+
(B)
17+
+----|-----+ Client Identifier +---------------+
18+
| -+----(A)-- & Redirection URI ---->| |
19+
| User- | | Authorization |
20+
| Agent -+----(B)-- User authenticates --->| Server |
21+
| | | |
22+
| -+----(C)-- Authorization Code ---<| |
23+
+-|----|---+ +---------------+
24+
| | ^ v
25+
(A) (C) | |
26+
| | | |
27+
^ v | |
28+
+---------+ | |
29+
| |>---(D)-- Authorization Code ---------' |
30+
| Client | & Redirection URI |
31+
| | |
32+
| |<---(E)----- Access Token -------------------'
33+
+---------+ (w/ Optional Refresh Token)
34+
```
35+
36+
### Provider dependencies
37+
38+
- @node-oauth/express-oauth-server (uses @node-oauth/oauth2-server)
39+
- express
40+
- body-parser
41+
42+
### Client dependencies
43+
44+
- express
45+
- ejs
46+
47+
## Installation and usage
48+
49+
Install dependencies in both provider and client directories:
50+
51+
```shell
52+
$ cd provider && npm install
53+
$ cd ../client && npm install
54+
```
55+
56+
Create a `.env` file in the authorization-code directory:
57+
58+
```
59+
CLIENT_ID=testclient
60+
CLIENT_SECRET=testsecret
61+
REDIRECT_URI=http://localhost:3000/callback
62+
USER_ID=user1
63+
USERNAME=demo
64+
PASSWORD=demo
65+
```
66+
67+
Start the provider (authorization server + resource server):
68+
69+
```shell
70+
$ cd provider && npm start
71+
```
72+
73+
Start the client application:
74+
75+
```shell
76+
$ cd client && npm start
77+
```
78+
79+
Visit http://localhost:3000 to start the authorization code flow.
80+
81+
## About This Example
82+
83+
This example demonstrates a clear separation between the OAuth2 provider (authorization server + resource server) and the client application. Unlike other examples that might combine both roles in a single application, this example shows:
84+
85+
- **Provider** (port 8080): Acts as both authorization server and resource server
86+
- **Client** (port 3000): A separate web application that consumes OAuth2 services
87+
88+
This separation makes it easier to understand what the framework supports and what it doesn't.
89+
90+
## Flow
91+
92+
1. User visits the client application at http://localhost:3000
93+
2. User clicks "Login" to start the authorization flow
94+
3. User is redirected to the provider's authorization page
95+
4. User enters credentials and grants authorization
96+
5. User is redirected back to the client with an authorization code
97+
6. Client exchanges the code for an access token
98+
7. Client can now access protected resources using the access token

authorization-code/client/index.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
require("dotenv").config({ path: "../.env" });
2+
const express = require("express");
3+
const crypto = require("crypto");
4+
5+
const app = express();
6+
const states = new Map();
7+
8+
app.set("view engine", "ejs");
9+
app.set("views", "./views");
10+
app.use(express.static("public"));
11+
12+
const authServer = "http://localhost:8080";
13+
const clientId = process.env.CLIENT_ID || "testclient";
14+
const clientSecret = process.env.CLIENT_SECRET || "testsecret";
15+
const redirectUri =
16+
process.env.REDIRECT_URI || "http://localhost:3000/callback";
17+
18+
function generateState() {
19+
return crypto.randomBytes(16).toString("hex");
20+
}
21+
22+
app.use(express.urlencoded({ extended: false }));
23+
app.use(express.json());
24+
25+
app.get("/", (req, res) => {
26+
res.render("index", {
27+
authServer: authServer,
28+
});
29+
});
30+
31+
app.get("/login", (req, res) => {
32+
const state = generateState();
33+
states.set(state, { created: Date.now() });
34+
35+
res.render("authorize", {
36+
client: { id: clientId },
37+
redirectUri: redirectUri,
38+
scope: "read write",
39+
state: state,
40+
authServer: authServer,
41+
});
42+
});
43+
44+
app.get("/callback", (req, res) => {
45+
const { code, state, error } = req.query;
46+
47+
if (error) {
48+
return res.render("error", {
49+
message: `Authorization Error: ${error}`,
50+
});
51+
}
52+
53+
if (!states.has(state)) {
54+
return res.render("error", {
55+
message: "Invalid State: State parameter mismatch",
56+
});
57+
}
58+
59+
states.delete(state);
60+
61+
res.render("callback", {
62+
code: code,
63+
state: state,
64+
authServer: authServer,
65+
clientId: clientId,
66+
clientSecret: clientSecret,
67+
redirectUri: redirectUri,
68+
});
69+
});
70+
71+
app.get("/logout", (req, res) => {
72+
res.redirect("/");
73+
});
74+
75+
app.listen(3000);
76+
console.debug("[Client]: listens to http://localhost:3000");
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "oauth2-authorization-code-client",
3+
"version": "1.0.0",
4+
"description": "OAuth2 Authorization Code Grant Client Example",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node index.js"
8+
},
9+
"dependencies": {
10+
"dotenv": "^16.0.3",
11+
"ejs": "^3.1.9",
12+
"express": "^4.18.2"
13+
}
14+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
:root {
2+
/* Color Variables */
3+
--primary-color: #007bff;
4+
--secondary-color: #6c757d;
5+
--danger-color: #dc3545;
6+
--success-color: #28a745;
7+
--white: #ffffff;
8+
9+
/* Background Colors */
10+
--light-bg: #f8f9fa;
11+
--success-bg: #d4edda;
12+
--danger-bg: #f5c6cb;
13+
14+
/* Border Colors */
15+
--light-border: #dee2e6;
16+
--success-border: #c3e6cb;
17+
--danger-border: #f5c6cb;
18+
--input-border: #ddd;
19+
20+
/* Spacing */
21+
--border-radius: 4px;
22+
--padding: 10px;
23+
--margin: 10px;
24+
}
25+
26+
body {
27+
font-family: Arial, sans-serif;
28+
max-width: 800px;
29+
margin: 50px auto;
30+
padding: var(--padding);
31+
}
32+
33+
* {
34+
box-sizing: border-box;
35+
}
36+
37+
.button {
38+
display: inline-block;
39+
padding: var(--padding);
40+
margin: var(--margin);
41+
text-decoration: none;
42+
border-radius: var(--border-radius);
43+
cursor: pointer;
44+
border: none;
45+
font-size: 14px;
46+
font-weight: normal;
47+
text-align: center;
48+
vertical-align: middle;
49+
}
50+
51+
.primary {
52+
background: var(--primary-color);
53+
color: var(--white);
54+
}
55+
56+
.secondary {
57+
background: var(--secondary-color);
58+
color: var(--white);
59+
}
60+
61+
.danger {
62+
background: var(--danger-color);
63+
color: var(--white);
64+
border: 1px solid var(--danger-border);
65+
}
66+
67+
.alert {
68+
padding: var(--padding);
69+
margin: var(--margin) 0;
70+
border-radius: var(--border-radius);
71+
}
72+
73+
.success {
74+
background: var(--success-bg);
75+
border: 1px solid var(--success-border);
76+
}
77+
78+
.info {
79+
background: var(--light-bg);
80+
padding: var(--padding);
81+
border: 1px solid var(--light-border);
82+
margin-bottom: var(--margin);
83+
}
84+
85+
.result {
86+
background: var(--light-bg);
87+
padding: var(--padding);
88+
border: 1px solid var(--light-border);
89+
border-radius: var(--border-radius);
90+
margin: var(--margin) 0;
91+
}
92+
93+
.form-group {
94+
margin-bottom: var(--margin);
95+
}
96+
97+
label {
98+
display: block;
99+
margin-bottom: var(--margin);
100+
}
101+
102+
input[type="text"],
103+
input[type="password"] {
104+
width: 100%;
105+
padding: var(--padding);
106+
border: 1px solid var(--input-border);
107+
border-radius: var(--border-radius);
108+
}
109+
110+
.code {
111+
background: var(--light-bg);
112+
padding: var(--padding);
113+
border: 1px solid var(--light-border);
114+
font-family: monospace;
115+
border-radius: var(--border-radius);
116+
}
117+
118+
pre {
119+
background: var(--light-bg);
120+
padding: var(--padding);
121+
border: 1px solid var(--light-border);
122+
border-radius: var(--border-radius);
123+
overflow-x: auto;
124+
}
125+
126+
.text-center {
127+
text-align: center;
128+
}
129+
130+
.status {
131+
padding: var(--padding);
132+
border-radius: var(--border-radius);
133+
margin: var(--margin) 0;
134+
}

0 commit comments

Comments
 (0)