Skip to content

Commit d09a1a0

Browse files
authored
Merge pull request #9 from node-oauth/authorization-code
Add example for Authorization Code grant.
2 parents 508ac98 + 11e99be commit d09a1a0

File tree

17 files changed

+2915
-0
lines changed

17 files changed

+2915
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ with different setups.
77

88
Each example contains its own README with specific installation and run instructions.
99

10+
### Authorization Code Flow
11+
12+
Location: [/authorization-code](./authorization-code)
13+
14+
Minimal authorization code flow with express and fetch and in-memory DB.
15+
1016
### Server to server
1117

1218
Location: [/server2server](./server2server)

authorization-code/README.md

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

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();
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+
console.log(process.env.AUTH_SERVER);
13+
const authServer = process.env.AUTH_SERVER;
14+
const clientId = process.env.CLIENT_ID;
15+
const clientSecret = process.env.CLIENT_SECRET;
16+
const redirectUri = process.env.REDIRECT_URI;
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");

0 commit comments

Comments
 (0)