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

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
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
107 changes: 107 additions & 0 deletions authorization-code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Authorization Code Grant Example

## 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

### Client dependencies

- express
- ejs

## Installation and usage

Install dependencies in both provider and client directories:

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

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
```

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
```

Start the provider (authorization server + resource server):

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

Start the client application:

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

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 framework 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