Skip to content

Add example for Authorization Code grant. #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ with different setups.

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

### Authorization Code Flow

Location: [/authorization-code](./authorization-code)

Minimal authorization code flow with express and fetch and in-memory DB.

### Server to server

Location: [/server2server](./server2server)
Expand Down
115 changes: 115 additions & 0 deletions authorization-code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Authorization Code Grant Example

> [!CAUTION]
> Do not use this example in production 1:1. It is meant for educational purposes and
> needs to be adapted to your specific use case. It is a minimal example that does not
> include all necessary security measures.

## Architecture

The authorization code workflow is described in
[RFC 6749, section 4.1](https://datatracker.ietf.org/doc/html/rfc6749.html#section-4.1):

```
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
```

### Provider dependencies

- @node-oauth/express-oauth-server (uses @node-oauth/oauth2-server)
- express
- body-parser
- cors

### Client dependencies

- express
- ejs

## Installation and usage

1. Install dependencies in both provider and client directories:

```shell
$ cd provider && npm install
$ cd ../client && npm install
```

2. Create a `.env` file in the authorization-code/provider directory:

```
CLIENT_ID=testclient
CLIENT_SECRET=testsecret
REDIRECT_URI=http://localhost:3000/callback
USER_ID=user1
USERNAME=demo
PASSWORD=demo
```

3. Create a `.env` file in the authorization-code/client directory:

```
AUTH_SERVER=http://localhost:8080
CLIENT_ID=testclient
CLIENT_SECRET=testsecret
REDIRECT_URI=http://localhost:3000/callback
```

4. Start the provider (authorization server + resource server):

```shell
$ cd provider && npm start
```

5. Start the client application:

```shell
$ cd client && npm start
```

6. Visit http://localhost:3000 to start the authorization code flow.

## About This Example

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:

- **Provider** (port 8080): Acts as both authorization server and resource server
- **Client** (port 3000): A separate web application that consumes OAuth2 services

This separation makes it easier to understand what the `@node-oauth/oauth2-server` library supports and what it doesn't.

## Flow

1. User visits the client application at http://localhost:3000
2. User clicks "Login" to start the authorization flow
3. User is redirected to the provider's authorization page
4. User enters credentials and grants authorization
5. User is redirected back to the client with an authorization code
6. Client exchanges the code for an access token
7. Client can now access protected resources using the access token
4 changes: 4 additions & 0 deletions authorization-code/client/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
AUTH_SERVER=http://localhost:8080
CLIENT_ID=testclient
CLIENT_SECRET=testsecret
REDIRECT_URI=http://localhost:3000/callback
76 changes: 76 additions & 0 deletions authorization-code/client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require("dotenv").config();
const express = require("express");
const crypto = require("crypto");

const app = express();
const states = new Map();

app.set("view engine", "ejs");
app.set("views", "./views");
app.use(express.static("public"));

console.log(process.env.AUTH_SERVER);
const authServer = process.env.AUTH_SERVER;
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
const redirectUri = process.env.REDIRECT_URI;

function generateState() {
return crypto.randomBytes(16).toString("hex");
}

app.use(express.urlencoded({ extended: false }));
app.use(express.json());

app.get("/", (req, res) => {
res.render("index", {
authServer: authServer,
});
});

app.get("/login", (req, res) => {
const state = generateState();
states.set(state, { created: Date.now() });

res.render("authorize", {
client: { id: clientId },
redirectUri: redirectUri,
scope: "read write",
state: state,
authServer: authServer,
});
});

app.get("/callback", (req, res) => {
const { code, state, error } = req.query;

if (error) {
return res.render("error", {
message: `Authorization Error: ${error}`,
});
}

if (!states.has(state)) {
return res.render("error", {
message: "Invalid State: State parameter mismatch",
});
}

states.delete(state);

res.render("callback", {
code: code,
state: state,
authServer: authServer,
clientId: clientId,
clientSecret: clientSecret,
redirectUri: redirectUri,
});
});

app.get("/logout", (req, res) => {
res.redirect("/");
});

app.listen(3000);
console.debug("[Client]: listens to http://localhost:3000");
Loading